diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 64e8000..a5ed8d4 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -5,137 +5,18 @@ on: - workflow_dispatch jobs: - test-build: - name: Build & Test - strategy: - matrix: - os: - - ubuntu-20.04 - - macos-11 - - windows-2019 - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Build - run: cargo build --all-features - - name: Test - uses: bp3d-actions/cargo@main - with: - check-name: cargo test (${{ matrix.os }}) - command: test - args: --all-features --no-fail-fast - token: ${{ secrets.GITHUB_TOKEN }} + Test: + uses: BlockProject3D/workflows/.github/workflows/Build_Test.yml@main - clippy: - name: Check | Clippy - if: ${{ always() }} - needs: test-build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - name: Run check - uses: bp3d-actions/clippy-check@main - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features + Analyze: + uses: BlockProject3D/workflows/.github/workflows/Analyze.yml@main + needs: Test - audit: - name: Check | Audit - if: ${{ always() }} - needs: test-build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Install Audit Tool - run: cargo install cargo-audit - - name: Run check - uses: bp3d-actions/audit-check@main - with: - token: ${{ secrets.GITHUB_TOKEN }} + Coverage: + uses: BlockProject3D/workflows/.github/workflows/Coverage.yml@main - fmt: - name: Format Code - if: ${{ always() && github.ref != 'refs/heads/master' }} - needs: - - clippy - - audit - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt - - name: Run code formatter - uses: bp3d-actions/rustfmt-check@main - with: - token: ${{ secrets.GITHUB_TOKEN }} - - version: - name: Get Version - needs: test-build - runs-on: ubuntu-latest - outputs: - name: ${{ steps.version.outputs.name }} - version: ${{ steps.version.outputs.version }} - isnew: ${{ steps.version.outputs.isnew }} - ispre: ${{ steps.version.outputs.ispre }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Get Version - id: version - uses: bp3d-actions/cargo-version@main - with: - mode: get - token: ${{ secrets.GITHUB_TOKEN }} - - create-pre-release: - name: Create Pre Release - needs: version - if: github.ref == 'refs/heads/develop' && needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Setup cargo - run: cargo login ${{ secrets.RELEASE_TOKEN }} - - name: Publish - run: cargo publish - - name: Create - uses: ncipollo/release-action@main - with: - tag: ${{ needs.version.outputs.version }} - commit: ${{ github.ref }} - prerelease: true - name: ${{ needs.version.outputs.name }} release ${{ needs.version.outputs.version }} - body: "[Link to crates.io](https://crates.io/crates/${{ needs.version.outputs.name }})" - - create-release-pr: - name: Create Release Pull Request - needs: version - if: needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'false' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Create Pull Request - uses: repo-sync/pull-request@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - destination_branch: master - pr_title: Release ${{ needs.version.outputs.version }} + Release: + uses: BlockProject3D/workflows/.github/workflows/Release.yml@main + needs: Test + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf2e432..b4df58a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,43 +6,7 @@ on: - master jobs: - version: - name: Get Version - runs-on: ubuntu-latest - outputs: - name: ${{ steps.version.outputs.name }} - version: ${{ steps.version.outputs.version }} - isnew: ${{ steps.version.outputs.isnew }} - ispre: ${{ steps.version.outputs.ispre }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Get Version - id: version - uses: bp3d-actions/cargo-version@main - with: - mode: get - token: ${{ secrets.GITHUB_TOKEN }} - - create-release: - name: Release - needs: version - if: needs.version.outputs.isnew == 'true' && needs.version.outputs.ispre == 'false' - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Setup cargo - run: cargo login ${{ secrets.RELEASE_TOKEN }} - - name: Publish - run: cargo publish - - name: Create - uses: ncipollo/release-action@main - with: - tag: ${{ needs.version.outputs.version }} - commit: ${{ github.ref }} - prerelease: false - name: ${{ needs.version.outputs.name }} release ${{ needs.version.outputs.version }} - body: "[Link to crates.io](https://crates.io/crates/${{ needs.version.outputs.name }})" + Publish: + uses: BlockProject3D/workflows/.github/workflows/Publish.yml@main + secrets: + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..784cf44 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "LuaJIT"] + path = LuaJIT + url = https://github.com/LuaJIT/LuaJIT.git diff --git a/Cargo.toml b/Cargo.toml index 35fff1b..2dd45a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,6 @@ -[package] -name = "test" -version = "0.1.0" -authors = ["Yuri Edward "] -edition = "2018" -description = "" -license = "BSD-3-Clause" -repository = "https://gitlab.com/bp3d/test" -readme = "./README.MD" -keywords = [] -categories = [] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[features] - +[workspace] +members = [ + "build", + "codegen", + "core" +] diff --git a/LuaJIT b/LuaJIT new file mode 160000 index 0000000..eec7a80 --- /dev/null +++ b/LuaJIT @@ -0,0 +1 @@ +Subproject commit eec7a8016c3381b949b5d84583800d05897fa960 diff --git a/build/Cargo.toml b/build/Cargo.toml new file mode 100644 index 0000000..fbd0328 --- /dev/null +++ b/build/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bp3d-lua-build" +version = "1.0.0-rc.1.0.0" +edition = "2024" +publish = false + +[dependencies] +bp3d-os = { version = "1.0.0-rc.4.2.1", features = ["fs"] } +phf = { version = "0.11.3", features = ["macros"] } +cc = "1.2.15" diff --git a/build/src/build/interface.rs b/build/src/build/interface.rs new file mode 100644 index 0000000..6e26ed2 --- /dev/null +++ b/build/src/build/interface.rs @@ -0,0 +1,49 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::BuildInfo; +use crate::util::CommandRunner; +use std::path::PathBuf; + +pub struct Lib { + pub name: String, + pub path: PathBuf, + pub dynamic: bool, +} + +pub trait Build { + fn build(info: &BuildInfo, runner: &CommandRunner) -> std::io::Result<()>; + fn post_build(info: &BuildInfo, runner: &CommandRunner) -> std::io::Result<()>; + fn get_linked_lib(info: &BuildInfo) -> Lib; + + fn run(info: &BuildInfo) -> std::io::Result { + Self::build(info, &CommandRunner::new("failed to build LuaJIT"))?; + Self::post_build(info, &CommandRunner::new("failed to post-build LuaJIT"))?; + Ok(Self::get_linked_lib(info)) + } +} diff --git a/build/src/build/linux.rs b/build/src/build/linux.rs new file mode 100644 index 0000000..d056c1b --- /dev/null +++ b/build/src/build/linux.rs @@ -0,0 +1,74 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::BuildInfo; +use crate::build::Build; +use crate::build::interface::Lib; +use crate::util::CommandRunner; +use std::process::Command; + +pub struct Linux; + +impl Build for Linux { + fn build(info: &BuildInfo, runner: &CommandRunner) -> std::io::Result<()> { + let soname = format!("TARGET_SONAME=libbp3d-luajit-{}.so", info.version()); + runner.run( + Command::new("make") + .arg(soname) + .current_dir(info.build_dir()), + ) + } + + fn post_build(info: &BuildInfo, _: &CommandRunner) -> std::io::Result<()> { + let filename = format!("libbp3d-luajit-{}.so", info.version()); + let path_to_so = info.build_dir().join("src").join("libluajit.so"); + let path_to_dylib = info.build_dir().join(&filename); + std::fs::copy(&path_to_so, path_to_dylib)?; + let path_to_dylib2 = info.target_dir().join(filename); + std::fs::copy(&path_to_so, path_to_dylib2)?; + std::fs::remove_file(path_to_so)?; + Ok(()) + } + + fn get_linked_lib(info: &BuildInfo) -> Lib { + if info.dynamic() { + let name = format!("bp3d-luajit-{}", info.version()); + Lib { + name, + path: info.build_dir().into(), + dynamic: true, + } + } else { + Lib { + name: "luajit".into(), + path: info.build_dir().join("src"), + dynamic: false, + } + } + } +} diff --git a/build/src/build/mac.rs b/build/src/build/mac.rs new file mode 100644 index 0000000..912460c --- /dev/null +++ b/build/src/build/mac.rs @@ -0,0 +1,88 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::build::Build; +use crate::build::interface::Lib; +use crate::util::CommandRunner; +use crate::{BuildInfo, Target}; +use std::io::{Error, ErrorKind}; +use std::process::Command; + +pub struct MacOS; + +impl Build for MacOS { + fn build(info: &BuildInfo, runner: &CommandRunner) -> std::io::Result<()> { + match info.target() { + Target::MacAarch64 => runner.run( + Command::new("make") + .env("MACOSX_DEPLOYMENT_TARGET", "11.0") + .current_dir(info.build_dir()), + ), + Target::MacAmd64 => runner.run( + Command::new("make") + .env("MACOSX_DEPLOYMENT_TARGET", "10.11") + .current_dir(info.build_dir()), + ), + _ => Err(Error::other("unsupported target")), + } + } + + fn post_build(info: &BuildInfo, runner: &CommandRunner) -> std::io::Result<()> { + let filename = format!("libbp3d-luajit-{}.dylib", info.version()); + let filename2 = format!("@rpath/libbp3d-luajit-{}.dylib", info.version()); + let path_to_so = info.build_dir().join("src"); + runner.run( + Command::new("install_name_tool") + .args(["-id", &filename2, "libluajit.so"]) + .current_dir(&path_to_so), + )?; + let path_to_dylib = info.build_dir().join(&filename); + std::fs::copy(path_to_so.join("libluajit.so"), path_to_dylib)?; + let path_to_dylib2 = info.target_dir().join(filename); + std::fs::copy(path_to_so.join("libluajit.so"), path_to_dylib2)?; + std::fs::remove_file(path_to_so.join("libluajit.so"))?; + Ok(()) + } + + fn get_linked_lib(info: &BuildInfo) -> Lib { + if info.dynamic() { + let name = format!("bp3d-luajit-{}", info.version()); + Lib { + name, + path: info.build_dir().into(), + dynamic: true, + } + } else { + Lib { + name: "luajit".into(), + path: info.build_dir().join("src"), + dynamic: false, + } + } + } +} diff --git a/build/src/build/mod.rs b/build/src/build/mod.rs new file mode 100644 index 0000000..16a8518 --- /dev/null +++ b/build/src/build/mod.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod interface; +mod linux; +mod mac; +mod windows; + +pub use interface::*; + +pub use linux::Linux; +pub use mac::MacOS; +pub use windows::Windows; diff --git a/build/src/build/windows.rs b/build/src/build/windows.rs new file mode 100644 index 0000000..f39fc28 --- /dev/null +++ b/build/src/build/windows.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::BuildInfo; +use crate::build::Build; +use crate::build::interface::Lib; +use crate::util::CommandRunner; +use std::io::{Error, ErrorKind}; +use std::process::Command; + +pub struct Windows; + +impl Build for Windows { + fn build(info: &BuildInfo, runner: &CommandRunner) -> std::io::Result<()> { + let mut cmd = Command::new("cmd"); + cmd.arg("/C").arg("msvcbuild.bat"); + let cl = cc::windows_registry::find_tool(info.target_name(), "cl.exe") + .ok_or(Error::other("unable to find cl.exe"))?; + for (k, v) in cl.env() { + cmd.env(k, v); + } + if info.target_name().contains("aarch64") { + cmd.env("VSCMD_ARG_HOST_ARCH", "arm64"); + cmd.env("VSCMD_ARG_TGT_ARCH", "arm64"); + } + if !info.dynamic() { + cmd.arg("static"); + } + let dllname = format!("libbp3d-luajit-{}.dll", info.version()); + let libname = format!("libbp3d-luajit-{}.lib", info.version()); + cmd.env("LJDLLNAME", dllname); + cmd.env("LJLIBNAME", libname); + runner.run(cmd.current_dir(info.build_dir().join("src"))) + } + + fn post_build(info: &BuildInfo, _: &CommandRunner) -> std::io::Result<()> { + if !info.dynamic() { + //Nothing to be done in non-dynamic builds. + return Ok(()); + } + let dllname = format!("libbp3d-luajit-{}.dll", info.version()); + let path_to_dll = info.build_dir().join("src").join(&dllname); + let path_to_dylib = info.build_dir().join(&dllname); + std::fs::copy(&path_to_dll, path_to_dylib)?; + let path_to_dylib2 = info.target_dir().join(dllname); + std::fs::copy(path_to_dll, path_to_dylib2)?; + Ok(()) + } + + fn get_linked_lib(info: &BuildInfo) -> Lib { + let libname = format!("libbp3d-luajit-{}", info.version()); + Lib { + name: libname, + path: info.build_dir().join("src"), + dynamic: info.dynamic(), + } + } +} diff --git a/build/src/info.rs b/build/src/info.rs new file mode 100644 index 0000000..10c5888 --- /dev/null +++ b/build/src/info.rs @@ -0,0 +1,99 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::Target; +use crate::build::{Build, Lib, Linux, MacOS, Windows}; +use std::io::{Error, ErrorKind}; +use std::path::{Path, PathBuf}; + +pub struct BuildInfoBase<'a> { + pub dynamic: bool, + pub target_name: &'a str, + pub build_dir: &'a Path, + pub manifest: &'a Path, +} + +pub struct BuildInfo<'a> { + base: BuildInfoBase<'a>, + target_dir: PathBuf, + crate_version: String, +} + +const VERSION: &str = "version = \""; + +impl<'a> BuildInfo<'a> { + pub fn new(base: BuildInfoBase<'a>) -> std::io::Result { + let manifest = std::fs::read_to_string(base.manifest)?; + let target_dir = base.build_dir.join("../../../.."); + let start = manifest + .find(VERSION) + .ok_or(Error::other("failed to find crate version"))?; + let version = &manifest[start + VERSION.len()..]; + let end = version + .find("\"") + .ok_or(Error::other("failed to find crate version"))?; + Ok(Self { + base, + target_dir, + crate_version: String::from(&version[..end]), + }) + } + + pub fn build_dir(&self) -> &Path { + self.base.build_dir + } + + pub fn dynamic(&self) -> bool { + self.base.dynamic + } + + pub fn target(&self) -> Target { + Target::get(self.base.target_name) + } + + pub fn target_name(&self) -> &str { + self.base.target_name + } + + pub fn target_dir(&self) -> &Path { + &self.target_dir + } + + pub fn version(&self) -> &str { + &self.crate_version + } + + pub fn build(self) -> std::io::Result { + match self.target() { + Target::MacAmd64 | Target::MacAarch64 => MacOS::run(&self), + Target::Linux => Linux::run(&self), + Target::Windows => Windows::run(&self), + Target::Unsupported => Err(Error::other("unsupported target")), + } + } +} diff --git a/build/src/lib.rs b/build/src/lib.rs new file mode 100644 index 0000000..abc51c8 --- /dev/null +++ b/build/src/lib.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod build; +mod info; +mod patch; +mod target; +mod util; + +pub use crate::info::{BuildInfo, BuildInfoBase}; +pub use crate::patch::Patch; +pub use crate::target::Target; diff --git a/build/src/patch.rs b/build/src/patch.rs new file mode 100644 index 0000000..84f9b68 --- /dev/null +++ b/build/src/patch.rs @@ -0,0 +1,103 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::util::CommandRunner; +use bp3d_os::fs::CopyOptions; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::process::Command; + +pub struct Patch { + src_path: PathBuf, + patch_dir: PathBuf, + patch_list: Vec, +} + +impl Patch { + pub fn new(patch_dir: &Path, luajit_src: &Path) -> std::io::Result { + let patch_dir = bp3d_os::fs::get_absolute_path(patch_dir)?; + let src_path = bp3d_os::fs::get_absolute_path(luajit_src)?; + CommandRunner::new("failed to revert").run( + Command::new("git") + .args(["checkout", "."]) + .current_dir(&src_path), + )?; + Ok(Patch { + patch_dir, + src_path, + patch_list: Vec::new(), + }) + } + + pub fn apply(&mut self, name: &str) -> std::io::Result<()> { + CommandRunner::new("failed to patch").run( + Command::new("git") + .args([ + OsStr::new("apply"), + self.patch_dir.join(format!("{}.patch", name)).as_os_str(), + ]) + .current_dir(&self.src_path), + )?; + self.patch_list.push(name.into()); + Ok(()) + } + + pub fn get_patch_list(&self) -> impl Iterator { + self.patch_list.iter().map(|v| &**v) + } + + pub fn apply_all<'a>( + mut self, + patches: impl IntoIterator, + out_path: &Path, + ) -> std::io::Result> { + for patch in patches { + self.apply(patch)?; + } + if !out_path.is_dir() { + bp3d_os::fs::copy( + &self.src_path, + out_path, + CopyOptions::new().exclude(OsStr::new(".git")), + )?; + } + Ok(self.get_patch_list().map(String::from).collect()) + } +} + +impl Drop for Patch { + fn drop(&mut self) { + CommandRunner::new("failed to revert") + .run( + Command::new("git") + .args(["checkout", "."]) + .current_dir(&self.src_path), + ) + .unwrap(); + } +} diff --git a/build/src/target.rs b/build/src/target.rs new file mode 100644 index 0000000..a78d06c --- /dev/null +++ b/build/src/target.rs @@ -0,0 +1,54 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use phf::phf_map; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum Target { + MacAmd64, + MacAarch64, + Linux, + Windows, + Unsupported, +} + +static TARGET_MAP: phf::Map<&'static str, Target> = phf_map! { + "aarch64-apple-darwin" => Target::MacAarch64, + "aarch64-unknown-linux-gnu" => Target::Linux, + "i686-pc-windows-msvc" => Target::Windows, + "x86_64-pc-windows-msvc" => Target::Windows, + "aarch64-pc-windows-msvc" => Target::Windows, + "x86_64-apple-darwin" => Target::MacAmd64, + "x86_64-unknown-linux-gnu" => Target::Linux +}; + +impl Target { + pub fn get(name: &str) -> Target { + *TARGET_MAP.get(name).unwrap_or(&Target::Unsupported) + } +} diff --git a/build/src/util.rs b/build/src/util.rs new file mode 100644 index 0000000..a3069e0 --- /dev/null +++ b/build/src/util.rs @@ -0,0 +1,49 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io::{Error, ErrorKind}; +use std::process::Command; + +pub struct CommandRunner { + msg: &'static str, +} + +impl CommandRunner { + pub fn new(msg: &'static str) -> CommandRunner { + Self { msg } + } + + pub fn run(&self, cmd: &mut Command) -> std::io::Result<()> { + let stat = cmd.status()?; + if stat.success() { + Ok(()) + } else { + Err(Error::other(self.msg)) + } + } +} diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..2d310e2 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bp3d-lua-codegen" +version = "0.1.0" +edition = "2021" #Due to gen be a reserved keyword! +publish = false + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +syn = { version = "2.0.98", features = ["full"] } +proc-macro2 = "1.0.26" diff --git a/codegen/src/gen/from_param.rs b/codegen/src/gen/from_param.rs new file mode 100644 index 0000000..4fb59f9 --- /dev/null +++ b/codegen/src/gen/from_param.rs @@ -0,0 +1,213 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::parser::enums::EnumVariant; +use crate::parser::structs::StructField; +use crate::parser::Parser; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::Generics; + +pub struct FromParam { + name: Ident, + generics: Generics, + is_index_based: bool, + is_simple_enum: bool, +} + +impl FromParam { + pub fn new(name: Ident, generics: Generics) -> Self { + Self { + name, + generics, + is_index_based: false, + is_simple_enum: true, + } + } +} + +pub struct Field { + name: Ident, + from_param: TokenStream, + try_from_param: TokenStream, +} + +impl Parser for FromParam { + type ParsedField = Field; + type ParsedVariant = Field; + + fn parse_field(&mut self, field: StructField) -> Self::ParsedField { + let name = field.unique_name; + let ty = field.ty; + self.is_index_based = field.unique_name_is_index; + let reader = if field.unique_name_is_index { + let index = field.index + 1; //Lua indices starts at 1, not 0. + quote! { bp3d_lua::ffi::lua::lua_rawgeti(vm.as_ptr(), index, #index as _) } + } else { + quote! { bp3d_lua::ffi::lua::lua_getfield(vm.as_ptr(), index, bp3d_lua::c_stringify!(#name).as_ptr()) } + }; + Field { + name: name.clone(), + from_param: quote! { + #reader; + top += 1; + let #name: #ty = bp3d_lua::vm::function::FromParam::from_param(vm, top); + }, + try_from_param: quote! { + unsafe { #reader }; + top += 1; + let #name: #ty = bp3d_lua::vm::function::FromParam::try_from_param(vm, top)?; + }, + } + } + + fn parse_variant(&mut self, variant: EnumVariant) -> Self::ParsedVariant { + match variant { + EnumVariant::SingleField(v) => { + self.is_simple_enum = false; + let ty = v.field.ty; + let name = self.name.clone(); + let variant = v.unique_name; + Field { + name: variant.clone(), + from_param: quote! { + match <#ty as bp3d_lua::vm::function::FromParam>::try_from_param(vm, index) { + Some(v) => return #name::#variant(v), + None => () + }; + }, + try_from_param: quote! { + match <#ty as bp3d_lua::vm::function::FromParam>::try_from_param(vm, index) { + Some(v) => return Some(#name::#variant(v)), + None => () + }; + }, + } + } + EnumVariant::MultiField(_) => panic!("Multi-field enum variants are not supported"), + EnumVariant::None(variant) => { + let name = self.name.clone(); + let vname = variant.to_string(); + Field { + name: variant.clone(), + from_param: quote! { + match enum_name == #vname { + true => return #name::#variant, + false => () + }; + }, + try_from_param: quote! { + match enum_name == #vname { + true => return Some(#name::#variant), + false => () + }; + }, + } + } + } + } + + fn gen_struct(self, parsed: Vec) -> TokenStream { + let name = self.name; + let generics = self.generics; + let lifetime = generics + .lifetimes() + .next() + .map(|v| v.into_token_stream()) + .unwrap_or(quote! { '_ }); + let from_params = parsed.iter().map(|field| &field.from_param); + let try_from_params = parsed.iter().map(|field| &field.try_from_param); + let values = parsed.iter().map(|field| &field.name); + let end = if self.is_index_based { + quote! { + #name(#(#values),*) + } + } else { + quote! { + #name { #(#values),* } + } + }; + quote! { + impl #generics bp3d_lua::vm::function::FromParam<#lifetime> for #name #generics { + unsafe fn from_param(vm: &#lifetime bp3d_lua::vm::Vm, index: i32) -> Self { + unsafe { bp3d_lua::ffi::laux::luaL_checktype(vm.as_ptr(), index, bp3d_lua::ffi::lua::Type::Table) }; + let mut top = vm.top(); + #(#from_params)* + #end + } + + fn try_from_param(vm: &#lifetime bp3d_lua::vm::Vm, index: i32) -> Option { + bp3d_lua::vm::value::util::ensure_type_equals(vm, index, bp3d_lua::ffi::lua::Type::Table).ok()?; + let mut top = vm.top(); + let mut f = || -> Option { + #(#try_from_params)* + Some(#end) + }; + match f() { + Some(v) => Some(v), + None => { + // Reset stack position. + unsafe { bp3d_lua::ffi::lua::lua_settop(vm.as_ptr(), top) }; + None + } + } + } + } + + unsafe impl #generics bp3d_lua::util::core::SimpleDrop for #name #generics { } + } + } + + fn gen_enum(self, parsed: Vec) -> TokenStream { + let name = self.name; + let generics = self.generics; + let lifetime = generics + .lifetimes() + .next() + .map(|v| v.into_token_stream()) + .unwrap_or(quote! { '_ }); + let from_params = parsed.iter().map(|field| &field.from_param); + let try_from_params = parsed.iter().map(|field| &field.try_from_param); + quote! { + impl #generics bp3d_lua::vm::function::FromParam<#lifetime> for #name #generics { + unsafe fn from_param(vm: &#lifetime bp3d_lua::vm::Vm, index: i32) -> Self { + #(#from_params)* + bp3d_lua::ffi::laux::luaL_error(vm.as_ptr(), "Unable to find a type satisfying constraints"); + std::hint::unreachable_unchecked() + } + + fn try_from_param(vm: &#lifetime bp3d_lua::vm::Vm, index: i32) -> Option { + #(#try_from_params)* + None + } + } + + unsafe impl #generics bp3d_lua::util::core::SimpleDrop for #name #generics { } + } + } +} diff --git a/codegen/src/gen/into_param.rs b/codegen/src/gen/into_param.rs new file mode 100644 index 0000000..9d29adc --- /dev/null +++ b/codegen/src/gen/into_param.rs @@ -0,0 +1,113 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::parser::enums::EnumVariant; +use crate::parser::structs::StructField; +use crate::parser::Parser; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{Generics, Index}; + +pub struct IntoParam { + name: Ident, + generics: Generics, +} + +impl IntoParam { + pub fn new(name: Ident, generics: Generics) -> Self { + Self { name, generics } + } +} + +impl Parser for IntoParam { + type ParsedField = TokenStream; + type ParsedVariant = TokenStream; + + fn parse_field(&mut self, field: StructField) -> Self::ParsedField { + if field.unique_name_is_index { + let name_idx = Index::from(field.index); + // Table indices starts at 1 rather than 0 in Lua. + let index = (field.index + 1) as i32; + quote! { + tbl.set(#index, self.#name_idx).unwrap(); + } + } else { + let name = field.unique_name; + quote! { + tbl.set(bp3d_lua::c_stringify!(#name), self.#name).unwrap(); + } + } + } + + fn parse_variant(&mut self, variant: EnumVariant) -> Self::ParsedVariant { + match variant { + EnumVariant::SingleField(v) => { + let name = v.unique_name; + let ty = v.field.ty; + quote! { + Self::#name(v) => <#ty as bp3d_lua::vm::function::IntoParam>::into_param(v, vm), + } + } + EnumVariant::MultiField(_) => panic!("Multi-field enum variants are not supported"), + EnumVariant::None(name) => { + let str = name.to_string(); + quote! { + Self::#name => <&str as bp3d_lua::vm::function::IntoParam>::into_param(#str, vm), + } + } + } + } + + fn gen_struct(self, parsed: Vec) -> TokenStream { + let name = self.name; + let generics = self.generics; + quote! { + unsafe impl #generics bp3d_lua::vm::function::IntoParam for #name #generics { + fn into_param(self, vm: &bp3d_lua::vm::Vm) -> u16 { + let mut tbl = bp3d_lua::vm::table::Table::new(vm); + #(#parsed)* + 1 + } + } + } + } + + fn gen_enum(self, parsed: Vec) -> TokenStream { + let name = self.name; + let generics = self.generics; + quote! { + unsafe impl #generics bp3d_lua::vm::function::IntoParam for #name #generics { + fn into_param(self, vm: &bp3d_lua::vm::Vm) -> u16 { + match self { + #(#parsed)* + } + } + } + } + } +} diff --git a/codegen/src/gen/lua_type.rs b/codegen/src/gen/lua_type.rs new file mode 100644 index 0000000..4aade3a --- /dev/null +++ b/codegen/src/gen/lua_type.rs @@ -0,0 +1,100 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::parser::enums::EnumVariant; +use crate::parser::structs::StructField; +use crate::parser::Parser; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::Generics; + +pub struct LuaType { + name: Ident, + generics: Generics, +} + +impl LuaType { + pub fn new(name: Ident, generics: Generics) -> Self { + Self { name, generics } + } +} + +impl Parser for LuaType { + type ParsedField = TokenStream; + type ParsedVariant = Vec; + + fn parse_field(&mut self, field: StructField) -> Self::ParsedField { + let ty = field.ty; + quote! { + types.append(&mut <#ty as bp3d_lua::vm::util::LuaType>::lua_type()); + } + } + + fn parse_variant(&mut self, field: EnumVariant) -> Self::ParsedVariant { + let mut tokens = Vec::new(); + match field { + EnumVariant::SingleField(v) => tokens.push(self.parse_field(v.field)), + EnumVariant::MultiField(v) => { + for v in v.fields { + tokens.push(self.parse_field(v)); + } + } + EnumVariant::None(_) => (), + } + tokens + } + + fn gen_struct(self, parsed: Vec) -> TokenStream { + let name = self.name; + let generics = self.generics; + quote! { + impl #generics bp3d_lua::vm::util::LuaType for #name #generics { + fn lua_type() -> Vec { + let mut types = Vec::new(); + #(#parsed)* + types + } + } + } + } + + fn gen_enum(self, parsed: Vec) -> TokenStream { + let name = self.name; + let generics = self.generics; + let tokens = parsed.into_iter().map(|v| quote! { #(#v)* }); + quote! { + impl #generics bp3d_lua::vm::util::LuaType for #name #generics { + fn lua_type() -> Vec { + let mut types = Vec::new(); + #(#tokens)* + types + } + } + } + } +} diff --git a/codegen/src/gen/mod.rs b/codegen/src/gen/mod.rs new file mode 100644 index 0000000..ddee92f --- /dev/null +++ b/codegen/src/gen/mod.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod from_param; +mod into_param; +mod lua_type; + +pub use from_param::FromParam; +pub use into_param::IntoParam; +pub use lua_type::LuaType; diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..890713c --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,90 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod gen; +mod parser; + +use crate::gen::{FromParam, IntoParam, LuaType}; +use crate::parser::Parser; +use proc_macro::TokenStream; +use proc_macro2::Ident; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(FromParam)] +pub fn from_param(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + .. + } = parse_macro_input!(input); + FromParam::new(ident, generics).parse(data).into() +} + +#[proc_macro_derive(IntoParam)] +pub fn into_param(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + .. + } = parse_macro_input!(input); + IntoParam::new(ident, generics).parse(data).into() +} + +#[proc_macro_derive(LuaType)] +pub fn lua_type(input: TokenStream) -> TokenStream { + let DeriveInput { + ident, + data, + generics, + .. + } = parse_macro_input!(input); + LuaType::new(ident, generics).parse(data).into() +} + +#[proc_macro] +pub fn decl_lua_plugin(input: TokenStream) -> TokenStream { + let ident = parse_macro_input!(input as Ident); + let crate_name = std::env::var("CARGO_PKG_NAME").unwrap(); + let func_name = format!( + "bp3d_lua_{}_register_{}", + crate_name.replace("-", "_"), + ident + ); + let func = Ident::new(&func_name, ident.span()); + let q = quote! { + #[no_mangle] + extern "C" fn #func(l: bp3d_lua::ffi::lua::State, error: *mut bp3d_lua::module::error::Error) -> bool { + let vm = unsafe { bp3d_lua::vm::Vm::from_raw(l) }; + unsafe { bp3d_lua::module::run_lua_register(&vm, #ident, *&mut error) } + } + }; + q.into() +} diff --git a/codegen/src/parser/enums.rs b/codegen/src/parser/enums.rs new file mode 100644 index 0000000..94b564d --- /dev/null +++ b/codegen/src/parser/enums.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::parser::structs::{StructField, StructParser}; +use proc_macro2::Ident; +use syn::{Fields, Variant}; + +pub struct EnumVariantSingle { + pub unique_name: Ident, + pub field: StructField, +} + +pub struct EnumVariantMulti { + pub unique_name: Ident, + pub fields: Vec, +} + +#[allow(clippy::large_enum_variant)] +pub enum EnumVariant { + SingleField(EnumVariantSingle), + MultiField(EnumVariantMulti), + None(Ident), +} + +pub struct EnumParser; + +impl EnumParser { + pub fn parse(&mut self, variant: Variant) -> EnumVariant { + let unique_name = variant.ident; + match variant.fields { + Fields::Named(v) => { + let mut parser = StructParser::new(); + let fields = v.named.into_iter().map(|v| parser.parse(v)); + EnumVariant::MultiField(EnumVariantMulti { + unique_name, + fields: fields.collect(), + }) + } + Fields::Unnamed(v) => { + let mut parser = StructParser::new(); + if v.unnamed.len() == 1 { + EnumVariant::SingleField(EnumVariantSingle { + unique_name, + field: parser.parse(v.unnamed.into_iter().next().unwrap()), + }) + } else { + let fields = v.unnamed.into_iter().map(|v| parser.parse(v)); + EnumVariant::MultiField(EnumVariantMulti { + unique_name, + fields: fields.collect(), + }) + } + } + Fields::Unit => EnumVariant::None(unique_name), + } + } +} diff --git a/codegen/src/parser/interface.rs b/codegen/src/parser/interface.rs new file mode 100644 index 0000000..673ef0f --- /dev/null +++ b/codegen/src/parser/interface.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::parser::enums::{EnumParser, EnumVariant}; +use crate::parser::structs::{StructField, StructParser}; +use proc_macro2::TokenStream; +use syn::Data; + +pub trait Parser: Sized { + type ParsedField; + type ParsedVariant; + + fn parse_field(&mut self, field: StructField) -> Self::ParsedField; + fn parse_variant(&mut self, variant: EnumVariant) -> Self::ParsedVariant; + + fn gen_struct(self, parsed: Vec) -> TokenStream; + fn gen_enum(self, parsed: Vec) -> TokenStream; + + fn parse(mut self, data: Data) -> TokenStream { + match data { + Data::Struct(v) => { + let mut parser = StructParser::new(); + let mut parsed = Vec::new(); + for v in v.fields { + parsed.push(self.parse_field(parser.parse(v))); + } + self.gen_struct(parsed) + } + Data::Enum(v) => { + let mut parser = EnumParser; + let mut parsed = Vec::new(); + for v in v.variants { + parsed.push(self.parse_variant(parser.parse(v))); + } + self.gen_enum(parsed) + } + _ => panic!("Unsupported type"), + } + } +} diff --git a/codegen/src/parser/mod.rs b/codegen/src/parser/mod.rs new file mode 100644 index 0000000..c1686d2 --- /dev/null +++ b/codegen/src/parser/mod.rs @@ -0,0 +1,33 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod enums; +mod interface; +pub mod structs; + +pub use interface::*; diff --git a/codegen/src/parser/structs.rs b/codegen/src/parser/structs.rs new file mode 100644 index 0000000..ef52b82 --- /dev/null +++ b/codegen/src/parser/structs.rs @@ -0,0 +1,59 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use proc_macro2::{Ident, Span}; +use syn::{Field, Type}; + +pub struct StructField { + pub unique_name: Ident, + pub ty: Type, + pub index: usize, + pub unique_name_is_index: bool, +} + +pub struct StructParser { + cur_index: usize, +} + +impl StructParser { + pub fn new() -> Self { + Self { cur_index: 0 } + } + + pub fn parse(&mut self, field: Field) -> StructField { + let index = Ident::new(&format!("value_{}", self.cur_index), Span::call_site()); + self.cur_index += 1; + let unique_name = field.ident.clone().unwrap_or(index); + StructField { + unique_name, + ty: field.ty, + index: self.cur_index - 1, + unique_name_is_index: field.ident.is_none(), + } + } +} diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..91777ff --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "bp3d-lua" +version = "1.0.0-rc.2.1.0" +authors = ["Yuri Edward "] +edition = "2021" +description = "Lua wrapper and base library for BP3D." +license = "BSD-3-Clause" +repository = "https://gitlab.com/bp3d/lua" +readme = "../README.MD" +keywords = ["lua"] +categories = ["graphics"] +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bp3d-util = { version = "2.2.0", features = ["simple-error", "format", "string"] } +bp3d-debug = "1.0.0-rc.6.2.0" +bp3d-os = { version = "1.0.0-rc.4.4.0", features = [], optional = true } +time = { version = "0.3.41", features = ["formatting"], optional = true } +itertools = { version = "0.14.0" } +bp3d-lua-codegen = { version = "0.1.0", path = "../codegen", optional = true } + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.59.0", features = ["Win32_System_Threading", "Win32_System_Kernel", "Win32_System_Diagnostics", "Win32_System_Diagnostics_Debug"], optional = true } + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2.170", optional = true } + +[dev-dependencies] +bp3d-lua-codegen = { version = "0.1.0", path = "../codegen" } + +[build-dependencies] +bp3d-lua-build = { version = "1.0.0-rc.1.0.0", path = "../build" } +rustc_version = { version = "0.4.1", optional = true } + +[features] +interrupt = ["libc", "windows-sys", "root-vm"] +dynamic = [] +module = ["libs-core", "bp3d-lua-codegen", "rustc_version"] +root-vm = [] +util-method = [] +util-function = [] +util-namespace = [] +util-module = ["module", "bp3d-os/module"] +libs-core = ["util-namespace"] +libs = ["libs-core", "time", "bp3d-os/time"] diff --git a/core/build.rs b/core/build.rs new file mode 100644 index 0000000..bf561a8 --- /dev/null +++ b/core/build.rs @@ -0,0 +1,101 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use bp3d_lua_build::build::Lib; +use bp3d_lua_build::{BuildInfo, BuildInfoBase, Patch}; +use std::path::{Path, PathBuf}; + +#[cfg(feature = "dynamic")] +const DYNAMIC: bool = true; +#[cfg(not(feature = "dynamic"))] +const DYNAMIC: bool = false; + +const PATCH_LIST: &[&str] = &[ + "lib_init", // Disable unsafe/un-sandboxed libs. + "lj_disable_jit", // Disable global JIT state changes from Lua code. + "disable_lua_load", // Disable loadstring, dostring, etc from base lib. + "lua_ext", // Ext library such as lua_ext_tab_len, etc. + "lua_load_no_bc", // Treat all inputs as strings (no bytecode allowed). + "windows_set_lib_names", // Allow setting LJLIBNAME and LJDLLNAME from environment variables. + "lua_ext_ccatch_error", // Throw lua errors which cannot be catched from lua standard + // pcall/xpcall but only from lua_pcall C API. +]; + +fn apply_patches(out_path: &Path) -> std::io::Result> { + Patch::new( + &Path::new("..").join("patch"), + &Path::new("..").join("LuaJIT"), + )? + .apply_all(PATCH_LIST.iter().copied(), out_path) +} + +fn run_build(build_dir: &Path) -> std::io::Result { + let manifest = std::env::var_os("CARGO_MANIFEST_PATH") + .map(PathBuf::from) + .expect("Failed to read manifest path"); + let target_name = std::env::var("TARGET").expect("Failed to read build target"); + let base = BuildInfoBase { + dynamic: DYNAMIC, + target_name: &target_name, + build_dir, + manifest: &manifest, + }; + BuildInfo::new(base)?.build() +} + +fn main() { + // Rerun this script if any of the patch files changed. + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=patch"); + + // Create build directory. + let out = std::env::var_os("OUT_DIR").expect("Failed to acquire output directory"); + let out_path = Path::new(&out).join("luajit-build"); + + // Apply patches to LuaJIT source code. + apply_patches(&out_path).expect("Failed to patch LuaJIT"); + + // Copy the source directory to the build directory. + println!("Internal LuaJIT build directory: {}", out_path.display()); + + // Build LuaJIT. + let lib = run_build(&out_path).expect("Failed to build LuaJIT"); + + // Attempt to setup linkage. + println!("cargo:rustc-link-search=native={}", lib.path.display()); + if lib.dynamic { + println!("cargo:rustc-link-lib=dylib={}", lib.name); + } else { + println!("cargo:rustc-link-lib=static={}", lib.name); + } + #[cfg(feature = "module")] + println!( + "cargo:rustc-env=RUSTC_VERSION={}", + rustc_version::version().unwrap() + ); +} diff --git a/core/src/ffi/ext.rs b/core/src/ffi/ext.rs new file mode 100644 index 0000000..31df226 --- /dev/null +++ b/core/src/ffi/ext.rs @@ -0,0 +1,62 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{RawInteger, RawNumber, State}; +use std::ffi::c_int; + +pub type MSize = u32; + +//--------------- +// Value reading +//--------------- +extern "C" { + pub fn lua_ext_fast_checknumber(l: State, numarg: c_int) -> RawNumber; + pub fn lua_ext_fast_checkinteger(l: State, numarg: c_int) -> RawInteger; +} + +//------- +// Other +//------- +extern "C" { + pub fn lua_ext_tab_len(l: State, idx: c_int, outsize: *mut MSize) -> c_int; + pub fn lua_ext_ccatch_error(l: State) -> u32; +} + +//----- +// JIT +//----- +extern "C" { + /// Sets the global mode of the JIT. + pub fn lua_ext_setjitmode(l: State, mode: c_int) -> c_int; + + /// Returns global flags of the JIT. + pub fn lua_ext_getjitflags(l: State) -> u32; + + /// Sets global JIT flags. + pub fn lua_ext_setjitflags(l: State, flags: u32) -> c_int; +} diff --git a/core/src/ffi/jit.rs b/core/src/ffi/jit.rs new file mode 100644 index 0000000..9b4ac5c --- /dev/null +++ b/core/src/ffi/jit.rs @@ -0,0 +1,89 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ffi::c_int; + +pub const MODE_FLUSH: c_int = 0x0200; +pub const MODE_ON: c_int = 0x0100; +pub const MODE_OFF: c_int = 0x0000; + +pub const F_ON: u32 = 0x00000001; + +pub const F_CPU: u32 = 0x00000010; + +#[cfg(target_arch = "x86_64")] +mod x86_64 { + use crate::ffi::jit::F_CPU; + + pub const F_SSE3: u32 = F_CPU; + pub const F_SSE4_1: u32 = F_CPU << 1; + pub const F_BMI2: u32 = F_CPU << 2; +} + +#[cfg(any(target_arch = "aarch64", target_arch = "arm"))] +mod arm { + use crate::ffi::jit::F_CPU; + + pub const F_ARMV6_: u32 = F_CPU; + pub const F_ARMV6T2_: u32 = F_CPU << 1; + pub const F_ARMV7: u32 = F_CPU << 2; + pub const F_ARMV8: u32 = F_CPU << 3; + pub const F_VFPV2: u32 = F_CPU << 4; + pub const F_VFPV3: u32 = F_CPU << 5; + + pub const F_ARMV6: u32 = F_ARMV6_ | F_ARMV6T2_ | F_ARMV7 | F_ARMV8; + pub const F_ARMV6T2: u32 = F_ARMV6T2_ | F_ARMV7 | F_ARMV8; + pub const F_VFP: u32 = F_VFPV2 | F_VFPV3; +} + +#[cfg(any(target_arch = "aarch64", target_arch = "arm"))] +pub use arm::*; + +#[cfg(target_arch = "x86_64")] +pub use x86_64::*; + +pub const F_OPT: u32 = 0x00010000; +pub const F_OPT_MASK: u32 = 0x0fff0000; + +pub const F_OPT_FOLD: u32 = F_OPT; +pub const F_OPT_CSE: u32 = F_OPT << 1; +pub const F_OPT_DCE: u32 = F_OPT << 2; +pub const F_OPT_FWD: u32 = F_OPT << 3; +pub const F_OPT_DSE: u32 = F_OPT << 4; +pub const F_OPT_NARROW: u32 = F_OPT << 5; +pub const F_OPT_LOOP: u32 = F_OPT << 6; +pub const F_OPT_ABC: u32 = F_OPT << 7; +pub const F_OPT_SINK: u32 = F_OPT << 8; +pub const F_OPT_FUSE: u32 = F_OPT << 9; +pub const F_OPT_FMA: u32 = F_OPT << 10; + +pub const F_OPT_0: u32 = 0; +pub const F_OPT_1: u32 = F_OPT_FOLD | F_OPT_CSE | F_OPT_DCE; +pub const F_OPT_2: u32 = F_OPT_1 | F_OPT_NARROW | F_OPT_LOOP; +pub const F_OPT_3: u32 = F_OPT_2 | F_OPT_FWD | F_OPT_DSE | F_OPT_ABC | F_OPT_SINK | F_OPT_FUSE; +pub const F_OPT_DEFAULT: u32 = F_OPT_3; diff --git a/core/src/ffi/laux.rs b/core/src/ffi/laux.rs new file mode 100644 index 0000000..c8aee89 --- /dev/null +++ b/core/src/ffi/laux.rs @@ -0,0 +1,124 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{RawInteger, RawNumber, State, ThreadStatus, Type}; +use std::ffi::{c_char, c_int, c_void}; + +//-------------------- +// State manipulation +//-------------------- +extern "C" { + pub fn luaL_newstate() -> State; + pub fn luaL_openlibs(l: State); +} + +//---------------- +// Error handling +//---------------- +extern "C" { + pub fn luaL_error(l: State, fmt: *const c_char, ...) -> c_int; + pub fn luaL_typerror(l: State, narg: c_int, tname: *const c_char) -> c_int; + pub fn luaL_argerror(l: State, numarg: c_int, extramsg: *const c_char) -> c_int; + + pub fn luaL_traceback(l: State, l1: State, msg: *const c_char, level: c_int); +} + +//--------------- +// Value reading +//--------------- +extern "C" { + pub fn luaL_checklstring(l: State, numarg: c_int, len: *mut usize) -> *const c_char; + pub fn luaL_optlstring( + l: State, + numarg: c_int, + def: *const c_char, + len: *mut usize, + ) -> *const c_char; + + pub fn luaL_checknumber(l: State, numarg: c_int) -> RawNumber; + pub fn luaL_optnumber(l: State, narg: c_int, def: RawNumber) -> RawNumber; + + pub fn luaL_checkinteger(l: State, numarg: c_int) -> RawInteger; + pub fn luaL_optinteger(l: State, narg: c_int, def: RawInteger) -> RawInteger; + + pub fn luaL_checkstack(l: State, sz: c_int, msg: *const c_char); + pub fn luaL_checktype(l: State, narg: c_int, t: Type); + pub fn luaL_checkany(l: State, narg: c_int); + + pub fn luaL_checkudata(l: State, ud: c_int, tname: *const c_char) -> *mut c_void; + pub fn luaL_testudata(l: State, ud: c_int, tname: *const c_char) -> *mut c_void; +} + +//------------------------ +// Metatable manipulation +//------------------------ +extern "C" { + pub fn luaL_newmetatable(l: State, tname: *const c_char) -> c_int; + pub fn luaL_setmetatable(l: State, tname: *const c_char); + pub fn luaL_getmetafield(l: State, obj: c_int, e: *const c_char) -> c_int; + pub fn luaL_callmeta(l: State, obj: c_int, e: *const c_char) -> c_int; +} + +//------------------------- +// Miscellaneous functions +//------------------------- +extern "C" { + pub fn luaL_where(l: State, lvl: c_int); + + pub fn luaL_checkoption( + l: State, + narg: c_int, + def: *const c_char, + lst: *const *const c_char, + ) -> c_int; +} + +//---------- +// Registry +//---------- +pub const NOREF: c_int = -2; +pub const REFNIL: c_int = -1; + +extern "C" { + pub fn luaL_ref(l: State, t: c_int) -> c_int; + pub fn luaL_unref(l: State, t: c_int, r: c_int); +} + +//------------------ +// Loading lua code +//------------------ +extern "C" { + pub fn luaL_loadfile(l: State, filename: *const c_char) -> ThreadStatus; + pub fn luaL_loadbuffer( + l: State, + buff: *const c_char, + sz: usize, + name: *const c_char, + ) -> ThreadStatus; + pub fn luaL_loadstring(l: State, s: *const c_char) -> ThreadStatus; +} diff --git a/core/src/ffi/lua.rs b/core/src/ffi/lua.rs new file mode 100644 index 0000000..a5307a3 --- /dev/null +++ b/core/src/ffi/lua.rs @@ -0,0 +1,245 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ffi::{c_char, c_double, c_int, c_void}; + +/// The maximum size of a lua chunkname. This is used by Vm::run_named_code for optimization. +pub const IDSIZE: usize = 60; + +pub const REGISTRYINDEX: c_int = -10000; +pub const ENVIRONINDEX: c_int = -10001; +pub const GLOBALSINDEX: c_int = -10002; + +pub const fn upvalueindex(i: c_int) -> c_int { + GLOBALSINDEX - i +} + +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ThreadStatus { + Ok = 0, + Yield = 1, + ErrRun = 2, + ErrSyntax = 3, + ErrMem = 4, + ErrErr = 5, + ErrCcatch = 6, +} + +#[repr(transparent)] +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct State(*mut c_void); + +pub type CFunction = extern "C-unwind" fn(l: State) -> i32; + +pub type Reader = extern "C-unwind" fn(l: State, ud: *mut c_void, sz: *mut usize) -> *const c_char; + +pub type Writer = extern "C" fn(l: State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int; + +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Type { + None = -1, + Nil = 0, + Boolean = 1, + LightUserdata = 2, + Number = 3, + String = 4, + Table = 5, + Function = 6, + Userdata = 7, + Thread = 8, +} + +pub type RawNumber = c_double; +pub type RawInteger = isize; + +//-------------------- +// State manipulation +//-------------------- +extern "C" { + pub fn lua_close(l: State); + + pub fn lua_atpanic(l: State, panicf: CFunction) -> CFunction; + + pub fn lua_newthread(l: State) -> State; +} + +//-------------------------- +// Basic stack manipulation +//-------------------------- +extern "C" { + pub fn lua_gettop(l: State) -> c_int; + pub fn lua_settop(l: State, idx: c_int); + pub fn lua_pushvalue(l: State, idx: c_int); + pub fn lua_remove(l: State, idx: c_int); + pub fn lua_insert(l: State, idx: c_int); + pub fn lua_replace(l: State, idx: c_int); + pub fn lua_checkstack(l: State, sz: c_int) -> c_int; + + pub fn lua_xmove(from: State, to: State, n: c_int); +} + +//------------------------------- +// Access functions (stack -> C) +//------------------------------- +extern "C" { + pub fn lua_isnumber(l: State, idx: c_int) -> c_int; + pub fn lua_isstring(l: State, idx: c_int) -> c_int; + pub fn lua_iscfunction(l: State, idx: c_int) -> c_int; + pub fn lua_isuserdata(l: State, idx: c_int) -> c_int; + pub fn lua_type(l: State, idx: c_int) -> Type; + pub fn lua_typename(l: State, tp: c_int) -> *const c_char; + + pub fn lua_equal(l: State, idx1: c_int, idx2: c_int) -> c_int; + pub fn lua_rawequal(l: State, idx1: c_int, idx2: c_int) -> c_int; + pub fn lua_lessthan(l: State, idx1: c_int, idx2: c_int) -> c_int; + + pub fn lua_tonumber(l: State, idx: c_int) -> RawNumber; + pub fn lua_tointeger(l: State, idx: c_int) -> RawInteger; + pub fn lua_toboolean(l: State, idx: c_int) -> c_int; + pub fn lua_tolstring(l: State, idx: c_int, len: *mut usize) -> *const c_char; + pub fn lua_objlen(l: State, idx: c_int) -> usize; + pub fn lua_tocfunction(l: State, idx: c_int) -> CFunction; + pub fn lua_touserdata(l: State, idx: c_int) -> *mut c_void; + pub fn lua_tothread(l: State, idx: c_int) -> State; + pub fn lua_topointer(l: State, idx: c_int) -> *const c_void; +} + +//------------------------------- +// Push functions (C -> stack) +//------------------------------- +extern "C" { + pub fn lua_pushnil(l: State); + pub fn lua_pushnumber(l: State, n: RawNumber); + pub fn lua_pushinteger(l: State, n: RawInteger); + pub fn lua_pushlstring(l: State, s: *const c_char, len: usize); + pub fn lua_pushstring(l: State, s: *const c_char); + //LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp); + pub fn lua_pushfstring(l: State, fmt: *const c_char, ...) -> *const c_char; + pub fn lua_pushcclosure(l: State, fun: CFunction, n: c_int); + pub fn lua_pushboolean(l: State, b: c_int); + pub fn lua_pushlightuserdata(l: State, p: *mut c_void); + pub fn lua_pushthread(l: State); +} + +//------------------------------- +// Get functions (Lua -> stack) +//------------------------------- +extern "C" { + pub fn lua_gettable(l: State, idx: c_int); + pub fn lua_getfield(l: State, idx: c_int, k: *const c_char); + pub fn lua_rawget(l: State, idx: c_int); + pub fn lua_rawgeti(l: State, idx: c_int, n: c_int); + pub fn lua_createtable(l: State, narr: c_int, nrec: c_int); + pub fn lua_newuserdata(l: State, sz: usize) -> *mut c_void; + pub fn lua_getmetatable(l: State, objindex: c_int) -> c_int; + pub fn lua_getfenv(l: State, idx: c_int); +} + +//------------------------------- +// Set functions (stack -> Lua) +//------------------------------- +extern "C" { + pub fn lua_settable(l: State, idx: c_int); + pub fn lua_setfield(l: State, idx: c_int, k: *const c_char); + pub fn lua_rawset(l: State, idx: c_int); + pub fn lua_rawseti(l: State, idx: c_int, n: c_int); + pub fn lua_setmetatable(l: State, objindex: c_int) -> c_int; + pub fn lua_setfenv(l: State, idx: c_int) -> c_int; +} + +//----------------------------------------------------- +// `load' and `call' functions (load and run Lua code) +//----------------------------------------------------- +extern "C" { + pub fn lua_call(l: State, nargs: c_int, nresults: c_int); + pub fn lua_pcall(l: State, nargs: c_int, nresults: c_int, errfunc: c_int) -> ThreadStatus; + pub fn lua_cpcall(l: State, func: CFunction, ud: *mut c_void) -> c_int; + pub fn lua_load( + l: State, + reader: Reader, + dt: *mut c_void, + chunkname: *const c_char, + ) -> ThreadStatus; + + pub fn lua_dump(l: State, writer: Writer, data: *mut c_void) -> c_int; +} + +//--------------------- +// Coroutine functions +//--------------------- +extern "C" { + pub fn lua_yield(l: State, nresults: c_int) -> c_int; + pub fn lua_resume(l: State, narg: c_int) -> ThreadStatus; + pub fn lua_status(l: State) -> ThreadStatus; +} + +//----------------------------------------- +// Garbage-collection function and options +//----------------------------------------- +#[repr(i32)] +pub enum Gc { + Stop = 0, + Restart = 1, + Collect = 2, + Count = 3, + Countb = 4, + Step = 5, + SetPause = 6, + SetStepMul = 7, + IsRunning = 9, +} + +extern "C" { + pub fn lua_gc(l: State, what: Gc, data: c_int) -> c_int; +} + +//------------------------- +// Miscellaneous functions +//------------------------- +pub type Debug = *mut c_void; +pub type Hook = extern "C-unwind" fn(l: State, debug: Debug); + +const HOOKCALL: c_int = 0; +const HOOKRET: c_int = 1; +const HOOKLINE: c_int = 2; +const HOOKCOUNT: c_int = 3; + +pub const MASKCALL: c_int = 1 << HOOKCALL; +pub const MASKRET: c_int = 1 << HOOKRET; +pub const MASKLINE: c_int = 1 << HOOKLINE; +pub const MASKCOUNT: c_int = 1 << HOOKCOUNT; + +extern "C" { + pub fn lua_error(l: State) -> c_int; + pub fn lua_next(l: State, idx: c_int) -> c_int; + pub fn lua_concat(l: State, n: c_int); + + pub fn lua_sethook(l: State, hook: Option, mask: c_int, count: c_int) -> c_int; +} diff --git a/core/src/ffi/mod.rs b/core/src/ffi/mod.rs new file mode 100644 index 0000000..e0b9bbd --- /dev/null +++ b/core/src/ffi/mod.rs @@ -0,0 +1,32 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod ext; +pub mod jit; +pub mod laux; +pub mod lua; diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..bf2b6b0 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,39 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//TODO: Support dynamic linking and modules by dynamic linking to luajit. +//TODO: Attempt to implement custom __index on userdata. + +pub mod ffi; +pub mod libs; +mod r#macro; +pub mod util; +pub mod vm; + +#[cfg(feature = "module")] +pub mod module; diff --git a/core/src/libs/interface.rs b/core/src/libs/interface.rs new file mode 100644 index 0000000..a0a44e7 --- /dev/null +++ b/core/src/libs/interface.rs @@ -0,0 +1,85 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::util::Namespace; +use crate::vm::Vm; + +pub trait Lib { + const NAMESPACE: &'static str; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()>; + + fn register(&self, vm: &Vm) -> crate::vm::Result<()> { + let mut namespace = Namespace::new(vm, Self::NAMESPACE)?; + self.load(&mut namespace)?; + Ok(()) + } +} + +macro_rules! impl_tuple_lib { + ($($t: ident: $id: tt),*) => { + impl<$($t: $crate::libs::Lib),*> $crate::libs::Lib for ($($t),*) { + const NAMESPACE: &'static str = ""; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + $( + self.$id.load(namespace)?; + )* + Ok(()) + } + + fn register(&self, vm: &Vm) -> crate::vm::Result<()> { + $( + self.$id.register(vm)?; + )* + Ok(()) + } + } + }; +} + +impl_tuple_lib!(T: 0, T1: 1); +impl_tuple_lib!(T: 0, T1: 1, T2: 2); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14, T15: 15); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14, T15: 15, T16: 16); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14, T15: 15, T16: 16, T17: 17); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14, T15: 15, T16: 16, T17: 17, T18: 18); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14, T15: 15, T16: 16, T17: 17, T18: 18, T19: 19); +impl_tuple_lib!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9, T10: 10, T11: 11, T12: 12, T13: 13, T14: 14, T15: 15, T16: 16, T17: 17, T18: 18, T19: 19, T20: 20); diff --git a/core/src/libs/lua/base.rs b/core/src/libs/lua/base.rs new file mode 100644 index 0000000..dc83b8d --- /dev/null +++ b/core/src/libs/lua/base.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::table::Table; + +const PATCH_LIST: &[&str] = &[ + "disable_lua_load", + "lib_init", + "lj_disable_jit", + "lua_ext", + "lua_load_no_bc", +]; + +pub struct Base; + +impl Lib for Base { + const NAMESPACE: &'static str = "bp3d.lua"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([("name", "bp3d-lua"), ("version", env!("CARGO_PKG_VERSION"))])?; + let mut patches = Table::with_capacity(namespace.vm(), PATCH_LIST.len(), 0); + for (i, name) in PATCH_LIST.iter().enumerate() { + // Lua indices starts at 1 not 0. + patches.set(i + 1, *name)?; + } + namespace.add([("patches", patches)]) + } +} diff --git a/core/src/libs/lua/call.rs b/core/src/libs/lua/call.rs new file mode 100644 index 0000000..07ebad6 --- /dev/null +++ b/core/src/libs/lua/call.rs @@ -0,0 +1,63 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_lib_func; +use crate::libs::interface::Lib; +use crate::util::Namespace; +use crate::vm::error::Error; +use crate::vm::function::types::RFunction; +use crate::vm::value::any::{AnyParam, UncheckedAnyReturn}; +use crate::vm::value::types::Function; + +decl_lib_func! { + fn pcall(vm: &Vm, func: Function) -> UncheckedAnyReturn { + let top = vm.top(); + true.into_param(vm); + let ret = func.call::(()); + let new_top = vm.top(); + match ret { + Ok(_) => unsafe { UncheckedAnyReturn::new(vm, (new_top - top) as _) }, + Err(e) => { + match e { + Error::Runtime(e) => unsafe { UncheckedAnyReturn::new(vm, (false, e.backtrace()).into_param(vm)) }, + e => unsafe { UncheckedAnyReturn::new(vm, (false, e.to_string()).into_param(vm)) } + } + } + } + } +} + +pub struct Call; + +impl Lib for Call { + const NAMESPACE: &'static str = "bp3d.lua"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([("pcall", RFunction::wrap(pcall))]) + } +} diff --git a/core/src/libs/lua/load.rs b/core/src/libs/lua/load.rs new file mode 100644 index 0000000..7e0cc0e --- /dev/null +++ b/core/src/libs/lua/load.rs @@ -0,0 +1,133 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::libs::interface::Lib; +use crate::util::Namespace; +use crate::vm::core::load::{Code, Script}; +use crate::vm::function::types::RFunction; +use crate::vm::value::any::{AnyParam, UncheckedAnyReturn}; +use crate::vm::value::types::Function; +use crate::{decl_closure, decl_lib_func}; +use bp3d_util::simple_error; +use std::path::{Path, PathBuf}; + +decl_lib_func! { + fn run_string(vm: &Vm, s: &str, chunkname: Option<&str>) -> crate::vm::Result { + let top = vm.top(); + let ret = match chunkname { + None => vm.run_code::(s), + Some(name) => vm.run::(Code::new(name, s.as_bytes())) + }; + ret.map(|_| unsafe { UncheckedAnyReturn::new(vm, (vm.top() - top) as _) }) + } +} + +decl_lib_func! { + fn load_string<'a>(vm: &Vm, s: &str, chunkname: Option<&str>) -> (Option>, Option) { + match chunkname { + None => match vm.load_code(s) { + Ok(v) => (Some(v), None), + Err(v) => (None, Some(v.to_string())) + }, + Some(name) => match vm.load(Code::new(name, s.as_bytes())) { + Ok(v) => (Some(v), None), + Err(v) => (None, Some(v.to_string())) + } + } + } +} + +simple_error! { + Error { + EscapeChroot(String) => "invalid path {}: attempt to escape chroot", + (impl From) Io(std::io::Error) => "io error: {}", + (impl From) Vm(crate::vm::error::Error) => "lua error: {}" + } +} + +fn parse_lua_path(chroot: &Path, path: &str) -> Result { + let iter = path.split("/"); + let mut cur_path = Vec::new(); + for component in iter { + if component == ".." { + if cur_path.pop().is_none() { + return Err(Error::EscapeChroot(path.into())); + } + } else if component != "." { + cur_path.push(component); + } + } + Ok(chroot.join(path)) +} + +decl_closure! { + fn load_file<'a> |chroot: &Path| (vm: &Vm, path: &str) -> (Option>, Option) { + let path = match parse_lua_path(chroot, path) { + Ok(v) => v, + Err(e) => return (None, Some(e.to_string())) + }; + let script = match Script::from_path(path) { + Ok(v) => v, + Err(e) => return (None, Some(e.to_string())) + }; + match vm.load(script) { + Ok(v) => (Some(v), None), + Err(e) => (None, Some(e.to_string())) + } + } +} + +decl_closure! { + fn run_file<'a> |chroot: &Path| (vm: &Vm, path: &str) -> Result { + let path = parse_lua_path(chroot, path)?; + let script = Script::from_path(path)?; + let top = vm.top(); + vm.run::(script)?; + unsafe { Ok(UncheckedAnyReturn::new(vm, (vm.top() - top) as _)) } + } +} + +pub struct Load<'a>(pub Option<&'a Path>); + +impl Lib for Load<'_> { + const NAMESPACE: &'static str = "bp3d.lua"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([ + ("runString", RFunction::wrap(run_string)), + ("loadString", RFunction::wrap(load_string)), + ])?; + if let Some(chroot) = self.0 { + namespace.add([ + ("loadFile", load_file(chroot)), + ("runFile", run_file(chroot)), + ])?; + } + Ok(()) + } +} diff --git a/core/src/libs/lua/mod.rs b/core/src/libs/lua/mod.rs new file mode 100644 index 0000000..abc3827 --- /dev/null +++ b/core/src/libs/lua/mod.rs @@ -0,0 +1,46 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod base; +mod call; +mod load; +mod options; +pub mod require; + +#[cfg(feature = "util-module")] +mod module; + +pub use base::Base; +pub use call::Call; +pub use load::Load; +pub use require::Require; + +pub use options::Lua; + +#[cfg(feature = "util-module")] +pub use module::Module; diff --git a/core/src/libs/lua/module.rs b/core/src/libs/lua/module.rs new file mode 100644 index 0000000..16ddc2d --- /dev/null +++ b/core/src/libs/lua/module.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_userdata_mut; +use crate::libs::Lib; +use crate::util::module::ModuleManager; +use crate::util::module::Result; +use crate::util::Namespace; +use crate::vm::Vm; +use bp3d_os::module::library::types::VirtualLibrary; +use std::path::PathBuf; + +pub struct Module { + builtins: &'static [&'static VirtualLibrary], + search_paths: Vec, +} + +impl Module { + pub fn new(builtins: &'static [&'static VirtualLibrary]) -> Self { + Self { + builtins, + search_paths: Vec::new(), + } + } + + pub fn add_search_path(&mut self, path: PathBuf) -> &mut Self { + self.search_paths.push(path); + self + } +} + +struct ModuleManagerWrapper(ModuleManager); + +decl_userdata_mut! { + impl ModuleManagerWrapper { + fn load(this: &mut ModuleManagerWrapper, vm: &Vm, lib: &str, plugin: &str) -> Result<()> { + this.0.load(lib, plugin, vm) + } + } +} + +impl Lib for Module { + const NAMESPACE: &'static str = ""; + + fn load(&self, _: &mut Namespace) -> crate::vm::Result<()> { + unreachable!() + } + + fn register(&self, vm: &Vm) -> crate::vm::Result<()> { + vm.register_userdata::(crate::vm::userdata::case::Camel)?; + let mut manager = ModuleManager::new(self.builtins); + for search_path in &self.search_paths { + manager.add_search_path(search_path.clone()); + } + vm.set_global(c"MODULES", ModuleManagerWrapper(manager)) + } +} diff --git a/core/src/libs/lua/options.rs b/core/src/libs/lua/options.rs new file mode 100644 index 0000000..cd7446f --- /dev/null +++ b/core/src/libs/lua/options.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::libs::lua::base::Base; +use crate::libs::lua::call::Call; +use crate::libs::lua::load::Load; +use crate::libs::lua::require::{Provider, Require}; +use crate::libs::Lib; +use std::path::Path; + +#[derive(Default)] +pub struct Lua<'a> { + pub(super) load_chroot_path: Option<&'a Path>, + pub(super) provider: Option>, +} + +impl<'a> Lua<'a> { + pub fn new() -> Self { + Self::default() + } + + pub fn load_chroot_path(mut self, path: &'a Path) -> Self { + self.load_chroot_path = Some(path); + self + } + + pub fn provider(mut self, provider: std::rc::Rc) -> Self { + self.provider = Some(provider); + self + } + + pub fn build(self) -> impl Lib + 'a { + ( + Base, + Call, + Load(self.load_chroot_path), + Require(self.provider.unwrap_or_default()), + ) + } +} diff --git a/core/src/libs/lua/require.rs b/core/src/libs/lua/require.rs new file mode 100644 index 0000000..475f6bb --- /dev/null +++ b/core/src/libs/lua/require.rs @@ -0,0 +1,98 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_closure; +use crate::libs::interface::Lib; +use crate::util::Namespace; +use crate::vm::closure::rc::Rc; +use crate::vm::value::any::{AnyParam, UncheckedAnyReturn}; +use crate::vm::Vm; +use bp3d_util::simple_error; +use std::cell::RefCell; +use std::collections::HashMap; + +simple_error! { + pub Error { + (impl From) Vm(crate::vm::error::Error) => "lua error: {}", + InvalidSyntax => "invalid syntax for require", + UnknownSource(String) => "unknown source name {}" + } +} + +pub trait Source { + fn run(&self, vm: &Vm, path: &str) -> crate::vm::Result; +} + +#[derive(Default)] +pub struct Provider(RefCell>>); + +impl Provider { + pub fn new() -> Self { + Provider::default() + } + + pub fn add_source(&self, name: String, source: S) { + let mut guard = self.0.borrow_mut(); + guard.insert(name, Box::new(source)); + } + + pub fn require(&self, vm: &Vm, path: &str) -> Result { + let id = path.find('.').ok_or(Error::InvalidSyntax)?; + let source = &path[..id]; + let guard = self.0.borrow(); + let src = guard + .get(source) + .ok_or_else(|| Error::UnknownSource(source.into()))?; + let ret = src.run(vm, path)?; + Ok(ret) + } +} + +decl_closure! { + fn require |provider: Rc| (vm: &Vm, path: &str) -> Result { + let top = vm.top(); + provider.require(vm, path)?; + unsafe { Ok(UncheckedAnyReturn::new(vm, (vm.top() - top) as _)) } + } +} + +pub struct Require(pub std::rc::Rc); + +impl Lib for Require { + const NAMESPACE: &'static str = "bp3d.lua"; + + fn load(&self, _: &mut Namespace) -> crate::vm::Result<()> { + std::unreachable!() + } + + fn register(&self, vm: &Vm) -> crate::vm::Result<()> { + let rc = Rc::from_rust(vm, self.0.clone()); + let mut namespace = Namespace::new(vm, "bp3d.lua")?; + namespace.add([("require", require(rc))]) + } +} diff --git a/core/src/libs/mod.rs b/core/src/libs/mod.rs new file mode 100644 index 0000000..fd4b885 --- /dev/null +++ b/core/src/libs/mod.rs @@ -0,0 +1,49 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// The bp3d-lua core library. +#[cfg(feature = "libs")] +pub mod lua; + +/// Utility toolkit. +#[cfg(feature = "libs")] +pub mod util; + +/// OS toolkit. +#[cfg(feature = "libs")] +pub mod os; + +#[cfg(feature = "libs-core")] +mod interface; +//TODO: maybe add a stack debug function which prints the content of the lua stack +//TODO: os lib with basic function (mainly time and performance management) and threading (sandbox with max number of threads) +// make sure thread join is time-limited. +//TODO: utf8 lib with string functions operating on UTF8-strings + +#[cfg(feature = "libs-core")] +pub use interface::*; diff --git a/core/src/libs/os/compat.rs b/core/src/libs/os/compat.rs new file mode 100644 index 0000000..4b6f871 --- /dev/null +++ b/core/src/libs/os/compat.rs @@ -0,0 +1,207 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_lib_func; +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::function::types::RFunction; +use crate::vm::function::IntoParam; +use crate::vm::table::Table; +use crate::vm::Vm; +use bp3d_os::time::{LocalUtcOffset, MonthExt}; +use bp3d_util::simple_error; +use std::time::Instant; +use time::format_description::parse; +use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; + +enum TableOrString<'a> { + Table(Table<'a>), + String(String), +} + +unsafe impl IntoParam for TableOrString<'_> { + fn into_param(self, vm: &Vm) -> u16 { + match self { + TableOrString::Table(t) => t.into_param(vm), + TableOrString::String(s) => s.into_param(vm), + } + } +} + +fn get_std_offset() -> UtcOffset { + let now = OffsetDateTime::now_utc(); + let jan = PrimitiveDateTime::new( + Date::from_calendar_date(now.year(), Month::January, 1).unwrap(), + Time::MIDNIGHT, + ) + .assume_utc(); + let jul = PrimitiveDateTime::new( + Date::from_calendar_date(now.year(), Month::July, 1).unwrap(), + Time::MIDNIGHT, + ) + .assume_utc(); + let offset_jan = UtcOffset::local_offset_at(jan).unwrap(); + let offset_jul = UtcOffset::local_offset_at(jul).unwrap(); + std::cmp::max(offset_jan, offset_jul) +} + +const REPLACEMENTS: &[(&str, &str)] = &[ + ("[", "[["), + ("%%", "%"), + ("%a", "[weekday repr:short]"), + ("%A", "[weekday repr:long]"), + ("%b", "[month repr:short]"), + ("%B", "[month repr:long]"), + ("%d", "[day]"), + ("%H", "[hour repr:24]"), + ("%I", "[hour repr:12]"), + ("%M", "[minute]"), + ("%m", "[month]"), + ("%p", "[period]"), + ("%S", "[second]"), + ("%w", "[weekday]"), + ("%Y", "[year]"), + ("%y", "[year repr:last_two]"), +]; + +decl_lib_func! { + fn date<'a>(vm: &Vm, format: Option<&str>, time: Option) -> Option> { + let mut format = format.unwrap_or("%c"); + let mut time = time.map(OffsetDateTime::from_unix_timestamp).unwrap_or(Ok(OffsetDateTime::now_utc())).ok()?; + if format.starts_with('!') { + format = &format[1..]; + } else { + let offset = UtcOffset::local_offset_at(time)?; + time = time.to_offset(offset); + } + if format == "*t" { + let std_offset = get_std_offset(); + let mut table = Table::new(vm); + table.set(c"sec", time.second()).unwrap(); + table.set(c"min", time.minute()).unwrap(); + table.set(c"hour", time.hour()).unwrap(); + table.set(c"day", time.day()).unwrap(); + table.set(c"month", time.month() as u8).unwrap(); + table.set(c"year", time.year()).unwrap(); + table.set(c"wday", time.weekday() as u8 + 1).unwrap(); + table.set(c"yday", time.to_julian_day()).unwrap(); + table.set(c"isdst", time.offset() < std_offset).unwrap(); + Some(TableOrString::Table(table)) + } else { + let mut format = String::from(format); + for (k, v) in REPLACEMENTS { + format = format.replace(k, v); + } + let format = parse(format.as_str()).ok()?; + time.format(&format).map(TableOrString::String).ok() + } + } +} + +simple_error! { + TimeFormatError { + (impl From)Vm(crate::vm::error::Error) => "vm error: {}", + InvalidMonthIndex(u8) => "invalid month index {}", + (impl From)Time(time::error::ComponentRange) => "out of range error: {}" + } +} + +fn get_time_from_table(table: Table) -> Result { + let year: i32 = table.get(c"year")?; + let month: u8 = table.get(c"month")?; + let day: u8 = table.get(c"day")?; + let date = Date::from_calendar_date( + year, + Month::from_index(month).ok_or(TimeFormatError::InvalidMonthIndex(month))?, + day, + )?; + let hour: Option = table.get(c"hour")?; + let minute: Option = table.get(c"min")?; + let second: Option = table.get(c"sec")?; + let mut hour = hour.unwrap_or(12); + let minute = minute.unwrap_or(0); + let second = second.unwrap_or(0); + let dst: Option = table.get(c"isdst")?; + let dst = dst.unwrap_or(false); + // Consider DST to be always +1H, this may not always be true but is true in most countries. + if dst { + hour += 1; + } + let time = Time::from_hms(hour, minute, second)?; + let time = PrimitiveDateTime::new(date, time).assume_utc(); + Ok(time) +} + +decl_lib_func! { + fn time(table: Option) -> Result { + match table { + Some(table) => get_time_from_table(table).map(|v| v.unix_timestamp()), + None => Ok(OffsetDateTime::now_utc().unix_timestamp()) + } + } +} + +decl_lib_func! { + fn difftime(a: i64, b: Option) -> Option { + let a = OffsetDateTime::from_unix_timestamp(a).ok()?; + let b = OffsetDateTime::from_unix_timestamp(b.unwrap_or(0)).ok()?; + Some((a - b).as_seconds_f64()) + } +} + +thread_local! { + static NOW: Instant = Instant::now(); +} + +decl_lib_func! { + fn clock() -> f64 { + NOW.with(|v| v.elapsed().as_secs_f64()) + } +} + +decl_lib_func! { + fn getenv(key: &str) -> Option> { + std::env::var_os(key).map(|v| v.into_encoded_bytes()) + } +} + +pub struct Compat; + +impl Lib for Compat { + const NAMESPACE: &'static str = "os"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([ + ("date", RFunction::wrap(date)), + ("time", RFunction::wrap(time)), + ("clock", RFunction::wrap(clock)), + ("difftime", RFunction::wrap(difftime)), + ("getenv", RFunction::wrap(getenv)), + ]) + } +} diff --git a/core/src/libs/os/instant.rs b/core/src/libs/os/instant.rs new file mode 100644 index 0000000..367149e --- /dev/null +++ b/core/src/libs/os/instant.rs @@ -0,0 +1,61 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::function::types::RFunction; +use crate::{decl_lib_func, decl_userdata}; + +struct Wrapper(bp3d_os::time::Instant); + +decl_userdata! { + impl Wrapper { + fn elapsed(this: &Wrapper) -> f64 { + this.0.elapsed().as_secs_f64() + } + } +} + +decl_lib_func! { + fn now() -> Wrapper { + Wrapper(bp3d_os::time::Instant::now()) + } +} + +pub struct Instant; + +impl Lib for Instant { + const NAMESPACE: &'static str = "bp3d.os.instant"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace + .vm() + .register_userdata::(crate::vm::userdata::case::Camel)?; + namespace.add([("now", RFunction::wrap(now))]) + } +} diff --git a/core/src/libs/os/mod.rs b/core/src/libs/os/mod.rs new file mode 100644 index 0000000..48f9a5b --- /dev/null +++ b/core/src/libs/os/mod.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod compat; +mod instant; +mod time; + +pub use compat::Compat; +pub use instant::Instant; +pub use time::Time; diff --git a/core/src/libs/os/time.rs b/core/src/libs/os/time.rs new file mode 100644 index 0000000..f4859ac --- /dev/null +++ b/core/src/libs/os/time.rs @@ -0,0 +1,183 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::function::types::RFunction; +use crate::vm::function::IntoParam; +use crate::vm::table::Table; +use crate::vm::Vm; +use crate::{decl_lib_func, decl_userdata}; +use bp3d_os::time::{LocalOffsetDateTime, MonthExt}; +use bp3d_util::simple_error; +use time::error::{ComponentRange, Format, InvalidFormatDescription}; +use time::format_description::parse; +use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, UtcOffset}; + +simple_error! { + FormatError { + (impl From)InvalidDescription(InvalidFormatDescription) => "invalid format description: {}", + (impl From)Format(Format) => "format error: {}" + } +} + +struct OffsetDateTimeWrapper(OffsetDateTime); + +decl_userdata! { + impl OffsetDateTimeWrapper { + fn format(this: &OffsetDateTimeWrapper, format: &str) -> Result { + let desc = parse(format)?; + let str = this.0.format(&desc)?; + Ok(str) + } + + fn __add(this: &OffsetDateTimeWrapper, duration: f64) -> Option { + this.0.checked_add(Duration::seconds_f64(duration)).map(OffsetDateTimeWrapper) + } + + fn __sub(this: &OffsetDateTimeWrapper, other: &OffsetDateTimeWrapper) -> f64 { + (this.0 - other.0).as_seconds_f64() + } + + fn __gt(this: &OffsetDateTimeWrapper, other: &OffsetDateTimeWrapper) -> bool { + this.0 > other.0 + } + + fn __ge(this: &OffsetDateTimeWrapper, other: &OffsetDateTimeWrapper) -> bool { + this.0 >= other.0 + } + + fn __lt(this: &OffsetDateTimeWrapper, other: &OffsetDateTimeWrapper) -> bool { + this.0 < other.0 + } + + fn __le(this: &OffsetDateTimeWrapper, other: &OffsetDateTimeWrapper) -> bool { + this.0 <= other.0 + } + + fn get_date<'a>(this: &OffsetDateTimeWrapper, vm: &Vm) -> crate::vm::Result> { + let mut table = Table::with_capacity(vm, 0, 3); + table.set(c"year", this.0.year())?; + table.set(c"month", this.0.month() as u8)?; + table.set(c"day", this.0.day())?; + Ok(table) + } + + fn get_time<'a>(this: &OffsetDateTimeWrapper, vm: &Vm) -> crate::vm::Result> { + let mut table = Table::with_capacity(vm, 0, 3); + table.set(c"hour", this.0.hour())?; + table.set(c"minute", this.0.minute())?; + table.set(c"second", this.0.second())?; + Ok(table) + } + + fn get_offset<'a>(this: &OffsetDateTimeWrapper, vm: &Vm) -> crate::vm::Result> { + let mut table = Table::with_capacity(vm, 0, 3); + table.set(c"hours", this.0.offset().whole_hours())?; + table.set(c"minutes", this.0.offset().whole_minutes())?; + table.set(c"seconds", this.0.offset().whole_seconds())?; + Ok(table) + } + } +} + +unsafe impl IntoParam for OffsetDateTime { + fn into_param(self, vm: &Vm) -> u16 { + OffsetDateTimeWrapper(self).into_param(vm) + } +} + +decl_lib_func! { + fn now_utc() -> OffsetDateTime { + OffsetDateTime::now_utc() + } +} + +decl_lib_func! { + fn now_local() -> Option { + OffsetDateTime::now_local() + } +} + +decl_lib_func! { + fn from_unix_timestamp(timestamp: i64) -> Result { + OffsetDateTime::from_unix_timestamp(timestamp) + } +} + +simple_error! { + DateTimeError { + (impl From)Vm(crate::vm::error::Error) => "vm error: {}", + InvalidMonthIndex(u8) => "invalid month index {}", + (impl From)Time(ComponentRange) => "out of range error: {}" + } +} + +decl_lib_func! { + fn new(table: Table) -> Result { + let year: i32 = table.get(c"year")?; + let month: u8 = table.get(c"month")?; + let day: u8 = table.get(c"day")?; + let date = Date::from_calendar_date(year, Month::from_index(month).ok_or(DateTimeError::InvalidMonthIndex(month))?, day)?; + let hour: Option = table.get(c"hour")?; + let minute: Option = table.get(c"min")?; + let second: Option = table.get(c"sec")?; + let hour = hour.unwrap_or(12); + let minute = minute.unwrap_or(0); + let second = second.unwrap_or(0); + let time = time::Time::from_hms(hour, minute, second)?; + let offset: Option
= table.get(c"offset")?; + if let Some(offset) = offset { + let offset_hours: i8 = offset.get(c"hours")?; + let offset_minutes: i8 = offset.get(c"minutes")?; + let offset_seconds: i8 = offset.get(c"seconds")?; + let offset = UtcOffset::from_hms(offset_hours, offset_minutes, offset_seconds)?; + Ok(OffsetDateTime::new_in_offset(date, time, offset)) + } else { + Ok(PrimitiveDateTime::new(date, time).assume_utc()) + } + } +} + +pub struct Time; + +impl Lib for Time { + const NAMESPACE: &'static str = "bp3d.os.time"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace + .vm() + .register_userdata::(crate::vm::userdata::case::Camel)?; + namespace.add([ + ("nowUtc", RFunction::wrap(now_utc)), + ("nowLocal", RFunction::wrap(now_local)), + ("fromUnixTimestamp", RFunction::wrap(from_unix_timestamp)), + ("new", RFunction::wrap(new)), + ]) + } +} diff --git a/core/src/libs/util/mod.rs b/core/src/libs/util/mod.rs new file mode 100644 index 0000000..4db407f --- /dev/null +++ b/core/src/libs/util/mod.rs @@ -0,0 +1,39 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod string; +mod table; +mod utf8; + +pub use string::String; +pub use table::Table; +pub use utf8::Utf8; + +// Workaround for language defect #22259. +#[allow(non_upper_case_globals)] +pub const Util: (Table, String, Utf8) = (Table, String, Utf8); diff --git a/core/src/libs/util/string.rs b/core/src/libs/util/string.rs new file mode 100644 index 0000000..4a8d4ee --- /dev/null +++ b/core/src/libs/util/string.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_lib_func; +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::function::types::RFunction; +use crate::vm::table::Table; +use bp3d_util::string::BufTools; +use std::borrow::Cow; + +decl_lib_func! { + fn contains(src: &[u8], needle: &[u8]) -> bool { + if needle.is_empty() { + return true; + } + src.windows(needle.len()).any(|window| window == needle) + } +} + +decl_lib_func! { + fn split<'a>(vm: &Vm, src: &[u8], pattern: u8) -> crate::vm::Result> { + let split = src.split(|v| *v == pattern); + let mut tbl = Table::new(vm); + for (i, v) in split.enumerate() { + // Indices starts at 1 in lua. + tbl.set(i + 1, v)?; + } + Ok(tbl) + } +} + +decl_lib_func! { + fn capitalise(src: &[u8]) -> Cow<[u8]> { + src.capitalise_ascii() + } +} + +decl_lib_func! { + fn decapitalise(src: &[u8]) -> Cow<[u8]> { + src.decapitalise_ascii() + } +} + +pub struct String; + +impl Lib for String { + const NAMESPACE: &'static str = "bp3d.util.string"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([ + ("contains", RFunction::wrap(contains)), + ("split", RFunction::wrap(split)), + ("capitalise", RFunction::wrap(capitalise)), + ("decapitalise", RFunction::wrap(decapitalise)), + ]) + } +} diff --git a/core/src/libs/util/table.rs b/core/src/libs/util/table.rs new file mode 100644 index 0000000..7e9020f --- /dev/null +++ b/core/src/libs/util/table.rs @@ -0,0 +1,182 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_lib_func; +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::function::types::RFunction; +use crate::vm::table::Table as LuaTable; +use crate::vm::value::any::AnyValue; +use crate::vm::Vm; +use bp3d_util::simple_error; + +fn update_rec(vm: &Vm, mut dst: LuaTable, mut src: LuaTable) -> crate::vm::Result<()> { + for res in src.iter() { + let (k, v) = res?; + match v { + AnyValue::Table(v) => vm.scope(|_| { + let dst1: Option = dst.get_any(k.clone())?; + match dst1 { + None => { + let tbl = LuaTable::new(vm); + update_rec(vm, tbl.clone(), v)?; + dst.set_any(k, tbl)?; + } + Some(v1) => update_rec(vm, v1, v)?, + } + Ok(()) + })?, + _ => dst.set_any(k, v)?, + } + } + Ok(()) +} + +decl_lib_func! { + fn update(vm: &Vm, dst: LuaTable, src: LuaTable) -> crate::vm::Result<()> { + update_rec(vm, dst, src) + } +} + +decl_lib_func! { + fn concat(vm: &Vm, dst: LuaTable) -> crate::vm::Result<()> { + let mut dst = dst; + let iter = crate::vm::core::iter::start::(vm, 2); + for res in iter { + let mut src = res?; + for res in src.iter() { + let (_, v) = res?; + dst.push(v)?; + } + } + Ok(()) + } +} + +decl_lib_func! { + fn copy<'a>(vm: &Vm, src: LuaTable) -> crate::vm::Result> { + let tbl = crate::vm::table::Table::new(vm); + update_rec(vm, tbl.clone(), src)?; + Ok(tbl) + } +} + +decl_lib_func! { + fn count(src: LuaTable) -> u64 { + src.len() as _ + } +} + +fn to_string_rec(prefix: String, mut table: LuaTable) -> crate::vm::Result> { + let mut lines = Vec::new(); + for res in table.iter() { + let (k, v) = res?; + match v { + AnyValue::Table(v) => { + lines.push(format!("{}:", k)); + lines.extend(to_string_rec(prefix.clone() + " ", v)?); + } + v => lines.push(format!("{}: {}", k, v)), + } + } + Ok(lines) +} + +decl_lib_func! { + fn to_string(src: LuaTable) -> crate::vm::Result { + to_string_rec("".into(), src).map(|v| v.join("\n")) + } +} + +decl_lib_func! { + fn contains(src: LuaTable, value: AnyValue) -> crate::vm::Result { + let mut src = src; + for res in src.iter() { + let (_, v) = res?; + if v == value { + return Ok(true) + } + } + Ok(false) + } +} + +decl_lib_func! { + fn contains_key(src: LuaTable, key: AnyValue) -> crate::vm::Result { + let mut src = src; + for res in src.iter() { + let (k, _) = res?; + if k == key { + return Ok(true) + } + } + Ok(false) + } +} + +simple_error! { + ProtectError { + NewIndex => "attempt to set value into protected table." + } +} + +decl_lib_func! { + fn __newindex() -> Result<(), ProtectError> { + Err(ProtectError::NewIndex) + } +} + +decl_lib_func! { + fn protect<'a>(vm: &Vm, src: LuaTable) -> crate::vm::Result> { + let mut wrapper = LuaTable::new(vm); + let mut metatable = LuaTable::new(vm); + metatable.set(c"__index", src)?; + metatable.set(c"__newindex", RFunction::wrap(__newindex))?; + wrapper.set_metatable(metatable); + Ok(wrapper) + } +} + +pub struct Table; + +impl Lib for Table { + const NAMESPACE: &'static str = "bp3d.util.table"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([ + ("update", RFunction::wrap(update)), + ("count", RFunction::wrap(count)), + ("tostring", RFunction::wrap(to_string)), + ("contains", RFunction::wrap(contains)), + ("containsKey", RFunction::wrap(contains_key)), + ("protect", RFunction::wrap(protect)), + ("copy", RFunction::wrap(copy)), + ("concat", RFunction::wrap(concat)), + ]) + } +} diff --git a/core/src/libs/util/utf8.rs b/core/src/libs/util/utf8.rs new file mode 100644 index 0000000..82439da --- /dev/null +++ b/core/src/libs/util/utf8.rs @@ -0,0 +1,139 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::decl_lib_func; +use crate::libs::Lib; +use crate::util::Namespace; +use crate::vm::function::types::RFunction; +use crate::vm::table::Table; +use bp3d_util::string::StrTools; +use std::borrow::Cow; + +decl_lib_func! { + fn contains(src: &str, needle: &str) -> bool { + src.contains(needle) + } +} + +decl_lib_func! { + fn split<'a>(vm: &Vm, src: &str, pattern: &str) -> crate::vm::Result> { + let split = src.split(pattern); + let mut tbl = Table::new(vm); + for (i, v) in split.enumerate() { + // Indices starts at 1 in lua. + tbl.set(i + 1, v)?; + } + Ok(tbl) + } +} + +decl_lib_func! { + fn replace(src: &str, pattern: &str, replacement: &str) -> String { + src.replace(pattern, replacement) + } +} + +decl_lib_func! { + fn count(src: &str) -> u32 { + src.chars().count() as u32 + } +} + +decl_lib_func! { + fn char_at(src: &str, pos: u32) -> Option { + src.chars().nth(pos as usize).map(|v| v as u32) + } +} + +decl_lib_func! { + fn from_string<'a>(src: &'a [u8]) -> Option<&'a str> { + std::str::from_utf8(src).ok() + } +} + +decl_lib_func! { + fn from_string_lossy(src: &[u8]) -> Cow { + String::from_utf8_lossy(src) + } +} + +decl_lib_func! { + fn capitalise(src: &str) -> Cow { + src.capitalise() + } +} + +decl_lib_func! { + fn decapitalise(src: &str) -> Cow { + src.decapitalise() + } +} + +decl_lib_func! { + fn upper(src: &str) -> String { + src.to_uppercase() + } +} + +decl_lib_func! { + fn lower(src: &str) -> String { + src.to_lowercase() + } +} + +decl_lib_func! { + fn sub(src: &str, start: u32, end: Option) -> &str { + match end { + None => src.sub_nearest((start as usize)..), + Some(v) => src.sub_nearest((start as usize)..(v as usize)) + } + } +} + +pub struct Utf8; + +impl Lib for Utf8 { + const NAMESPACE: &'static str = "bp3d.util.utf8"; + + fn load(&self, namespace: &mut Namespace) -> crate::vm::Result<()> { + namespace.add([ + ("contains", RFunction::wrap(contains)), + ("split", RFunction::wrap(split)), + ("replace", RFunction::wrap(replace)), + ("count", RFunction::wrap(count)), + ("charAt", RFunction::wrap(char_at)), + ("fromString", RFunction::wrap(from_string)), + ("fromStringLossy", RFunction::wrap(from_string_lossy)), + ("capitalise", RFunction::wrap(capitalise)), + ("decapitalise", RFunction::wrap(decapitalise)), + ("upper", RFunction::wrap(upper)), + ("lower", RFunction::wrap(lower)), + ("sub", RFunction::wrap(sub)), + ]) + } +} diff --git a/core/src/macro/closure.rs b/core/src/macro/closure.rs new file mode 100644 index 0000000..5076564 --- /dev/null +++ b/core/src/macro/closure.rs @@ -0,0 +1,71 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! decl_closure { + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? |$upvalue_name: ident: $upvalue_ty: ty| ($name: ident: &Vm$(, $($arg_name: ident: $arg_ty: ty),*)?) -> $ret_ty: ty $code: block + ) => { + $vis fn $fn_name(upvalue: $upvalue_ty) -> $crate::vm::closure::types::RClosure<$upvalue_ty> { + extern "C-unwind" fn _cfunc(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($name: &$($lifetime)? $crate::vm::Vm, $upvalue_name: <$upvalue_ty as $crate::vm::closure::Upvalue>::From<'_>$(, $($arg_name: $arg_ty),*)?) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + let $upvalue_name: <$upvalue_ty as $crate::vm::closure::Upvalue>::From<'_> = unsafe { $crate::vm::closure::FromUpvalue::from_upvalue(vm, 1) }; + $($crate::decl_from_param_unchecked!(vm, 1, $($arg_name: $arg_ty)*);)? + let ret = _func(vm, $upvalue_name $(, $($arg_name),*)?); + ret.into_param(vm) as _ + } + _vmfunc(&vm) + } + $crate::vm::closure::types::RClosure::new(_cfunc, upvalue) + } + }; + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? |$upvalue_name: ident: $upvalue_ty: ty| ($($arg_name: ident: $arg_ty: ty),*) -> $ret_ty: ty $code: block + ) => { + $vis fn $fn_name(upvalue: $upvalue_ty) -> $crate::vm::closure::types::RClosure<$upvalue_ty> { + extern "C-unwind" fn _cfunc(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($upvalue_name: <$upvalue_ty as $crate::vm::closure::Upvalue>::From<'_>, $($arg_name: $arg_ty),*) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + let $upvalue_name: <$upvalue_ty as $crate::vm::closure::Upvalue>::From<'_> = unsafe { $crate::vm::closure::FromUpvalue::from_upvalue(vm, 1) }; + $crate::decl_from_param_unchecked!(vm, 1, $($arg_name: $arg_ty)*); + let ret = _func($upvalue_name, $($arg_name),*); + ret.into_param(vm) as _ + } + _vmfunc(&vm) + } + $crate::vm::closure::types::RClosure::new(_cfunc, upvalue) + } + }; +} diff --git a/core/src/macro/lib_func.rs b/core/src/macro/lib_func.rs new file mode 100644 index 0000000..860abd0 --- /dev/null +++ b/core/src/macro/lib_func.rs @@ -0,0 +1,63 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! decl_lib_func { + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($name: ident: &Vm$(, $($arg_name: ident: $arg_ty: ty),*)?) -> $ret_ty: ty $code: block + ) => { + $vis extern "C-unwind" fn $fn_name(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($name: &$($lifetime)? $crate::vm::Vm$(, $($arg_name: $arg_ty),*)?) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + $($crate::decl_from_param_unchecked!(vm, 1, $($arg_name: $arg_ty)*);)? + let ret = _func(vm $(, $($arg_name),*)?); + ret.into_param(vm) as _ + } + _vmfunc(&vm) + } + }; + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($($arg_name: ident: $arg_ty: ty),*) -> $ret_ty: ty $code: block + ) => { + $vis extern "C-unwind" fn $fn_name(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($($arg_name: $arg_ty),*) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + $crate::decl_from_param_unchecked!(vm, 1, $($arg_name: $arg_ty)*); + let ret = _func($($arg_name),*); + ret.into_param(vm) as _ + } + _vmfunc(&vm) + } + }; +} diff --git a/core/src/macro/mod.rs b/core/src/macro/mod.rs new file mode 100644 index 0000000..686f930 --- /dev/null +++ b/core/src/macro/mod.rs @@ -0,0 +1,76 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod closure; +mod lib_func; +mod userdata; +mod userdata_func; + +#[macro_export] +macro_rules! c_stringify { + ($str: ident) => { + unsafe { std::ffi::CStr::from_ptr(concat!(stringify!($str), "\0").as_ptr() as _) } + }; +} + +/// This macro is unsafe and should not be used from safe code directly. It is intended as a +/// building block for other macros. +#[macro_export] +macro_rules! decl_from_param_unchecked { + ( + $vm: ident, $start_index: literal, + ) => { + }; + + ( + $vm: ident, $start_index: literal, $arg_name: ident: $arg_ty: ty + ) => { + use $crate::vm::function::FromParam; + let $arg_name: $arg_ty = unsafe { FromParam::from_param($vm, $start_index) }; + }; + + ( + $vm: ident, $start_index: literal, $($arg_name: ident: $arg_ty: ty)* + ) => { + use $crate::vm::function::FromParam; + let mut index = $start_index; + $crate::decl_from_param_unchecked!(_from_param $vm, index, $(($arg_name: $arg_ty))*); + }; + + (_from_param $vm: ident, $index: ident, ) => { }; + + (_from_param $vm: ident, $index: ident, ($arg_name: ident: $arg_ty: ty)) => { + let $arg_name: $arg_ty = unsafe { FromParam::from_param($vm, $index) }; + }; + + (_from_param $vm: ident, $index: ident, ($arg_name: ident: $arg_ty: ty) $(($arg_name2: ident: $arg_ty2: ty))*) => { + let $arg_name: $arg_ty = unsafe { FromParam::from_param($vm, $index) }; + $index += 1; + $crate::decl_from_param_unchecked!(_from_param $vm, $index, $(($arg_name2: $arg_ty2))*); + }; +} diff --git a/core/src/macro/userdata.rs b/core/src/macro/userdata.rs new file mode 100644 index 0000000..ab1313a --- /dev/null +++ b/core/src/macro/userdata.rs @@ -0,0 +1,86 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! _impl_userdata { + ($obj_name: ident, $($fn_name: ident),*) => { + impl $crate::vm::userdata::UserData for $obj_name { + const CLASS_NAME: &'static std::ffi::CStr = $crate::c_stringify!($obj_name); + + fn register(registry: &$crate::vm::userdata::core::Registry) -> std::result::Result<(), $crate::vm::userdata::Error> { + $( + let f = $obj_name::$fn_name()?; + registry.add_method(f); + )* + use $crate::vm::userdata::AddGcMethod; + (&$crate::vm::userdata::core::AddGcMethodAuto::<$obj_name>::default()).add_gc_method(registry); + Ok(()) + } + } + }; +} + +#[macro_export] +macro_rules! decl_userdata { + ( + impl $obj_name: ident { + $( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($this: ident: &$obj_name2: ident $($tokens: tt)*) -> $ret_ty: ty $code: block + )* + } + ) => { + $( + $crate::decl_userdata_func! { + $vis fn $fn_name $(<$lifetime>)? ($this: &$obj_name $($tokens)*) -> $ret_ty $code + } + )* + + $crate::_impl_userdata!($obj_name, $($fn_name),*); + + unsafe impl $crate::vm::userdata::UserDataImmutable for $obj_name {} + }; +} + +#[macro_export] +macro_rules! decl_userdata_mut { + ( + impl $obj_name: ident { + $( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($($tokens: tt)*) -> $ret_ty: ty $code: block + )* + } + ) => { + $( + $crate::decl_userdata_func! { + $vis fn $fn_name $(<$lifetime>)? ($($tokens)*) -> $ret_ty $code + } + )* + + $crate::_impl_userdata!($obj_name, $($fn_name),*); + }; +} diff --git a/core/src/macro/userdata_func.rs b/core/src/macro/userdata_func.rs new file mode 100644 index 0000000..384af6a --- /dev/null +++ b/core/src/macro/userdata_func.rs @@ -0,0 +1,133 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_export] +macro_rules! decl_userdata_func { + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($this: ident: &mut $obj_name: ident, $name: ident: &Vm$(, $($arg_name: ident: $arg_ty: ty),*)?) -> $ret_ty: ty $code: block + ) => { + impl $obj_name { + $vis fn $fn_name() -> std::result::Result<$crate::vm::userdata::core::Function, $crate::vm::userdata::Error> { + extern "C-unwind" fn _cfunc(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($this: &mut $obj_name, $name: &$($lifetime)? $crate::vm::Vm$(, $($arg_name: $arg_ty),*)?) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let this_ptr = unsafe { $crate::ffi::laux::luaL_checkudata(l, 1, <$obj_name as $crate::vm::userdata::UserData>::CLASS_NAME.as_ptr()) } as *mut $obj_name; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (this_ptr: *mut $obj_name, vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + $($crate::decl_from_param_unchecked!(vm, 2, $($arg_name: $arg_ty)*);)? + let ret = _func(unsafe { &mut *this_ptr }, vm $(, $($arg_name),*)?); + ret.into_param(vm) as _ + } + _vmfunc(this_ptr, &vm) + } + let mut f = $crate::vm::userdata::core::Builder::new($crate::c_stringify!($fn_name), _cfunc); + f.mutable(); + f.arg::<&$obj_name>(); + $($(f.arg::<$arg_ty>();)*)? + unsafe { f.build() } + } + } + }; + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($this: ident: &mut $obj_name: ident$(, $($arg_name: ident: $arg_ty: ty),*)?) -> $ret_ty: ty $code: block + ) => { + impl $obj_name { + $vis fn $fn_name() -> std::result::Result<$crate::vm::userdata::core::Function, $crate::vm::userdata::Error> { + extern "C-unwind" fn _cfunc(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($this: &mut $obj_name$(, $($arg_name: $arg_ty),*)?) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let this_ptr = unsafe { $crate::ffi::laux::luaL_checkudata(l, 1, <$obj_name as $crate::vm::userdata::UserData>::CLASS_NAME.as_ptr()) } as *mut $obj_name; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (this_ptr: *mut $obj_name, vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + $($crate::decl_from_param_unchecked!(vm, 2, $($arg_name: $arg_ty)*);)? + let ret = _func(unsafe { &mut *this_ptr } $(, $($arg_name),*)?); + ret.into_param(vm) as _ + } + _vmfunc(this_ptr, &vm) + } + let mut f = $crate::vm::userdata::core::Builder::new($crate::c_stringify!($fn_name), _cfunc); + f.mutable(); + f.arg::<&$obj_name>(); + $($(f.arg::<$arg_ty>();)*)? + unsafe { f.build() } + } + } + }; + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($this: ident: &$obj_name: ident, $name: ident: &Vm$(, $($arg_name: ident: $arg_ty: ty),*)?) -> $ret_ty: ty $code: block + ) => { + impl $obj_name { + $vis fn $fn_name() -> std::result::Result<$crate::vm::userdata::core::Function, $crate::vm::userdata::Error> { + extern "C-unwind" fn _cfunc(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($this: &$obj_name, $name: &$($lifetime)? $crate::vm::Vm$(, $($arg_name: $arg_ty),*)?) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let this_ptr = unsafe { $crate::ffi::laux::luaL_checkudata(l, 1, <$obj_name as $crate::vm::userdata::UserData>::CLASS_NAME.as_ptr()) } as *const $obj_name; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (this_ptr: *const $obj_name, vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + $($crate::decl_from_param_unchecked!(vm, 2, $($arg_name: $arg_ty)*);)? + let ret = _func(unsafe { &*this_ptr }, vm $(, $($arg_name),*)?); + ret.into_param(vm) as _ + } + _vmfunc(this_ptr, &vm) + } + let mut f = $crate::vm::userdata::core::Builder::new($crate::c_stringify!($fn_name), _cfunc); + f.arg::<&$obj_name>(); + $($(f.arg::<$arg_ty>();)*)? + unsafe { f.build() } + } + } + }; + ( + $vis: vis fn $fn_name: ident $(<$lifetime: lifetime>)? ($this: ident: &$obj_name: ident$(, $($arg_name: ident: $arg_ty: ty),*)?) -> $ret_ty: ty $code: block + ) => { + impl $obj_name { + $vis fn $fn_name() -> std::result::Result<$crate::vm::userdata::core::Function, $crate::vm::userdata::Error> { + extern "C-unwind" fn _cfunc(l: $crate::ffi::lua::State) -> i32 { + fn _func $(<$lifetime>)? ($this: &$obj_name$(, $($arg_name: $arg_ty),*)?) -> $ret_ty $code + use $crate::vm::function::IntoParam; + let this_ptr = unsafe { $crate::ffi::laux::luaL_checkudata(l, 1, <$obj_name as $crate::vm::userdata::UserData>::CLASS_NAME.as_ptr()) } as *const $obj_name; + let vm = unsafe { $crate::vm::Vm::from_raw(l) }; + #[inline(always)] + extern "C-unwind" fn _vmfunc $(<$lifetime>)? (this_ptr: *const $obj_name, vm: &$($lifetime)? $crate::vm::Vm) -> i32 { + $($crate::decl_from_param_unchecked!(vm, 2, $($arg_name: $arg_ty)*);)? + let ret = _func(unsafe { &*this_ptr } $(, $($arg_name),*)?); + ret.into_param(vm) as _ + } + _vmfunc(this_ptr, &vm) + } + let mut f = $crate::vm::userdata::core::Builder::new($crate::c_stringify!($fn_name), _cfunc); + f.arg::<&$obj_name>(); + $($(f.arg::<$arg_ty>();)*)? + unsafe { f.build() } + } + } + }; +} diff --git a/core/src/module/error.rs b/core/src/module/error.rs new file mode 100644 index 0000000..95742c0 --- /dev/null +++ b/core/src/module/error.rs @@ -0,0 +1,116 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::Type; +use std::ffi::c_char; + +pub const STRING_BUF_LEN: usize = 4096; + +#[derive(Copy, Clone)] +#[repr(i32)] +pub enum ErrorType { + None = 0, + Utf8 = -1, + Type = -2, + Syntax = -3, + Runtime = -4, + Memory = -5, + Unknown = -6, + Error = -7, + Null = -8, + MultiValue = -9, + UnsupportedType = -10, + Loader = -11, + ParseFloat = -12, + ParseInt = -13, + UncatchableRuntime = -14, + UserDataArgsEmpty = 1, + UserDataMutViolation = 2, + UserDataGc = 3, + UserDataIndex = 4, + UserDataMetatable = 5, + UserDataMultiValueField = 6, + UserDataAlreadyRegistered = 7, + UserDataAlignment = 8, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct Utf8Error { + pub ty: ErrorType, + pub valid_up_to: usize, + pub error_len: i16, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct TypeError { + pub ty: ErrorType, + pub expected: Type, + pub actual: Type, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct UnsupportedType { + pub ty: ErrorType, + pub actual: Type, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct String { + pub ty: ErrorType, + pub data: [u8; STRING_BUF_LEN], + pub len: usize, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct StaticString { + pub ty: ErrorType, + pub data: *const c_char, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct Alignment { + pub ty: ErrorType, + pub alignment: usize, +} + +#[repr(C)] +pub union Error { + pub ty: ErrorType, + pub string: String, + pub type_mismatch: TypeError, + pub utf8: Utf8Error, + pub unsupported_type: UnsupportedType, + pub static_string: StaticString, + pub alignment: Alignment, +} diff --git a/core/src/module/mod.rs b/core/src/module/mod.rs new file mode 100644 index 0000000..c93b143 --- /dev/null +++ b/core/src/module/mod.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod error; + +/// The BP3D Lua & LuaJIT version. +pub static VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// The version of the time library used by bp3d-lua. +pub static TIME_VERSION: &str = "0.3.41"; + +/// The macro which generates a plugin entry point. +pub use bp3d_lua_codegen::decl_lua_plugin; + +/// Helper function to run the [register](crate::libs::Lib::register) function of a +/// [Lib](crate::libs::Lib). +/// +/// This function automatically translates the Rust result type to the C FFI compatible type. +pub fn run_lua_register( + vm: &crate::vm::Vm, + lib: impl crate::libs::Lib, + error: &mut error::Error, +) -> bool { + use crate::vm::error::Error; + use bp3d_util::format::MemBufStr; + use std::fmt::Write; + let res = lib.register(vm); + match res { + Ok(()) => true, + Err(e) => { + match e { + Error::InvalidUtf8(e) => { + error.ty = error::ErrorType::Utf8; + // Option is not FFI safe, so use i16. + error.utf8.error_len = e.error_len().map(|v| v as i16).unwrap_or(-1); + error.utf8.valid_up_to = e.valid_up_to(); + } + Error::Type(e) => { + error.ty = error::ErrorType::Type; + error.type_mismatch.actual = e.actual; + error.type_mismatch.expected = e.expected; + } + Error::Syntax(e) => { + error.ty = error::ErrorType::Syntax; + let mut msg = + unsafe { MemBufStr::wrap(&mut error.string.len, &mut error.string.data) }; + let _ = write!(msg, "{}", e); + } + Error::Runtime(e) => { + error.ty = error::ErrorType::Runtime; + let mut msg = + unsafe { MemBufStr::wrap(&mut error.string.len, &mut error.string.data) }; + let _ = write!(msg, "{}", e); + } + Error::Memory => error.ty = error::ErrorType::Memory, + Error::Unknown => error.ty = error::ErrorType::Unknown, + Error::Error => error.ty = error::ErrorType::Error, + Error::Null => error.ty = error::ErrorType::Null, + Error::MultiValue => error.ty = error::ErrorType::MultiValue, + Error::UserData(e) => match e { + crate::vm::userdata::Error::ArgsEmpty => { + error.ty = error::ErrorType::UserDataArgsEmpty + } + crate::vm::userdata::Error::MutViolation(e) => { + error.ty = error::ErrorType::UserDataMutViolation; + error.static_string.data = e.as_ptr(); + } + crate::vm::userdata::Error::Gc => error.ty = error::ErrorType::UserDataGc, + crate::vm::userdata::Error::Index => error.ty = error::ErrorType::UserDataIndex, + crate::vm::userdata::Error::Metatable => { + error.ty = error::ErrorType::UserDataMetatable + } + crate::vm::userdata::Error::MultiValueField => { + error.ty = error::ErrorType::UserDataMultiValueField + } + crate::vm::userdata::Error::AlreadyRegistered(e) => { + error.ty = error::ErrorType::UserDataAlreadyRegistered; + error.static_string.data = e.as_ptr(); + } + crate::vm::userdata::Error::Alignment(e) => { + error.ty = error::ErrorType::UserDataAlignment; + error.alignment.alignment = e; + } + }, + Error::UnsupportedType(e) => { + error.ty = error::ErrorType::UnsupportedType; + error.unsupported_type.actual = e; + } + Error::Loader(e) => { + error.ty = error::ErrorType::Loader; + let mut msg = + unsafe { MemBufStr::wrap(&mut error.string.len, &mut error.string.data) }; + let _ = write!(msg, "{}", e); + } + Error::ParseInt => error.ty = error::ErrorType::ParseInt, + Error::ParseFloat => error.ty = error::ErrorType::ParseFloat, + Error::UncatchableRuntime(e) => { + error.ty = error::ErrorType::UncatchableRuntime; + let mut msg = + unsafe { MemBufStr::wrap(&mut error.string.len, &mut error.string.data) }; + let _ = write!(msg, "{}", e); + } + } + false + } + } +} diff --git a/core/src/util/core.rs b/core/src/util/core.rs new file mode 100644 index 0000000..9b7f6e0 --- /dev/null +++ b/core/src/util/core.rs @@ -0,0 +1,70 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Core rust utilities module. + +use std::borrow::Cow; +use std::ffi::{CStr, CString, OsStr}; +use std::path::Path; + +pub trait AnyStr { + fn to_str(&self) -> crate::vm::Result>; +} + +impl AnyStr for &str { + fn to_str(&self) -> crate::vm::Result> { + Ok(Cow::Owned( + CString::new(&**self).map_err(|_| crate::vm::error::Error::Null)?, + )) + } +} + +impl AnyStr for &CStr { + #[inline(always)] + fn to_str(&self) -> crate::vm::Result> { + Ok(Cow::Borrowed(&**self)) + } +} + +/// Represents a type which can be trivially dropped (i.e. no Drop implementation). +/// +/// # Safety +/// +/// This is UB to implement this trait on types which are not trivially dropped. +pub unsafe trait SimpleDrop {} + +unsafe impl SimpleDrop for *mut T {} +unsafe impl SimpleDrop for *const T {} +unsafe impl SimpleDrop for bool {} +unsafe impl SimpleDrop for &str {} +unsafe impl SimpleDrop for Option {} +unsafe impl SimpleDrop for Result {} +unsafe impl SimpleDrop for &T {} +unsafe impl SimpleDrop for &[u8] {} +unsafe impl SimpleDrop for &OsStr {} +unsafe impl SimpleDrop for &Path {} diff --git a/core/src/util/function.rs b/core/src/util/function.rs new file mode 100644 index 0000000..2e94c37 --- /dev/null +++ b/core/src/util/function.rs @@ -0,0 +1,58 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::vm::core::util::{pcall, push_error_handler}; +use crate::vm::registry::core::Key; +use crate::vm::registry::types::Function; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; + +/// This represents a Lua callback. +pub struct LuaFunction(Key); + +impl LuaFunction { + pub fn create(f: crate::vm::value::types::Function) -> Self { + Self(Key::new(f)) + } + + pub fn call<'a, R: FromLua<'a>>( + &self, + vm: &'a Vm, + value: impl IntoLua, + ) -> crate::vm::Result { + let pos = unsafe { push_error_handler(vm.as_ptr()) }; + unsafe { self.0.as_raw().push(vm) }; + let num_values = value.into_lua(vm); + unsafe { pcall(vm, num_values as _, R::num_values() as _, pos)? }; + R::from_lua(vm, -(R::num_values() as i32)) + } + + pub fn delete(self, vm: &Vm) { + self.0.delete(vm) + } +} diff --git a/core/src/util/method.rs b/core/src/util/method.rs new file mode 100644 index 0000000..09bcdb5 --- /dev/null +++ b/core/src/util/method.rs @@ -0,0 +1,70 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::util::core::AnyStr; +use crate::vm::core::util::{pcall, push_error_handler}; +use crate::vm::registry::core::Key; +use crate::vm::registry::types::{Function, Table}; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; + +pub struct LuaMethod { + obj: Key
, + method: Key, +} + +impl LuaMethod { + pub fn create( + obj: crate::vm::table::Table, + method_name: impl AnyStr, + ) -> crate::vm::Result { + let method: crate::vm::value::types::Function = obj.get(method_name)?; + Ok(Self { + method: Key::new(method), + obj: Key::new(obj), + }) + } + + pub fn call<'a, R: FromLua<'a>>( + &self, + vm: &'a Vm, + value: impl IntoLua, + ) -> crate::vm::Result { + let pos = unsafe { push_error_handler(vm.as_ptr()) }; + unsafe { self.method.as_raw().push(vm) }; + unsafe { self.obj.as_raw().push(vm) }; + let num_values = value.into_lua(vm); + unsafe { pcall(vm, (num_values + 1) as _, R::num_values() as _, pos)? }; + R::from_lua(vm, -(R::num_values() as i32)) + } + + pub fn delete(self, vm: &Vm) { + self.method.delete(vm); + self.obj.delete(vm); + } +} diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs new file mode 100644 index 0000000..f2a65c0 --- /dev/null +++ b/core/src/util/mod.rs @@ -0,0 +1,44 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod core; +#[cfg(feature = "util-function")] +mod function; +#[cfg(feature = "util-method")] +mod method; +#[cfg(feature = "util-module")] +pub mod module; +#[cfg(feature = "util-namespace")] +mod namespace; + +#[cfg(feature = "util-function")] +pub use function::LuaFunction; +#[cfg(feature = "util-method")] +pub use method::LuaMethod; +#[cfg(feature = "util-namespace")] +pub use namespace::Namespace; diff --git a/core/src/util/module.rs b/core/src/util/module.rs new file mode 100644 index 0000000..d386527 --- /dev/null +++ b/core/src/util/module.rs @@ -0,0 +1,220 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::module::error::ErrorType; +use crate::module::{TIME_VERSION, VERSION}; +use crate::vm::error::{RuntimeError, TypeError, Utf8Error}; +use crate::vm::Vm; +use bp3d_debug::info; +use bp3d_os::module::library::types::VirtualLibrary; +use bp3d_os::module::library::Library; +use bp3d_os::module::{Module, ModuleLoader}; +use bp3d_util::simple_error; +use std::collections::HashSet; +use std::ffi::CStr; +use std::path::PathBuf; + +simple_error! { + pub Error { + PluginAlreadyLoaded(String) => "plugin {} is already loaded", + LibNotFound(String) => "library not found: {}", + PluginNotFound(String) => "plugin not found: {}", + (impl From)Vm(crate::vm::error::Error) => "vm error: {}", + (impl From)Module(bp3d_os::module::error::Error) => "module error: {}" + } +} + +pub type Result = std::result::Result; + +type PluginFunc = + extern "C" fn(l: crate::ffi::lua::State, error: *mut crate::module::error::Error) -> bool; + +unsafe fn get_string( + err: &crate::module::error::Error, + f: impl FnOnce(String) -> crate::vm::error::Error, +) -> crate::vm::error::Error { + match std::str::from_utf8(&err.string.data[..err.string.len]) { + Ok(v) => f(v.into()), + Err(e) => crate::vm::error::Error::InvalidUtf8(e.into()), + } +} + +unsafe fn convert_module_error_to_vm_error( + err: crate::module::error::Error, +) -> crate::vm::error::Error { + match err.ty { + ErrorType::Utf8 => crate::vm::error::Error::InvalidUtf8(Utf8Error { + valid_up_to: err.utf8.valid_up_to, + error_len: if err.utf8.error_len < 0 { + None + } else { + Some(err.utf8.error_len as u8) + }, + }), + ErrorType::Type => crate::vm::error::Error::Type(TypeError { + expected: err.type_mismatch.expected, + actual: err.type_mismatch.actual, + }), + ErrorType::Syntax => get_string(&err, crate::vm::error::Error::Syntax), + ErrorType::Runtime => get_string(&err, |v| { + crate::vm::error::Error::Runtime(RuntimeError::new(v)) + }), + ErrorType::UncatchableRuntime => get_string(&err, |v| { + crate::vm::error::Error::UncatchableRuntime(RuntimeError::new(v)) + }), + ErrorType::Memory => crate::vm::error::Error::Memory, + ErrorType::Unknown => crate::vm::error::Error::Unknown, + ErrorType::Error => crate::vm::error::Error::Error, + ErrorType::Null => crate::vm::error::Error::Null, + ErrorType::MultiValue => crate::vm::error::Error::MultiValue, + ErrorType::UnsupportedType => { + crate::vm::error::Error::UnsupportedType(err.unsupported_type.actual) + } + ErrorType::Loader => get_string(&err, crate::vm::error::Error::Loader), + ErrorType::ParseFloat => crate::vm::error::Error::ParseFloat, + ErrorType::ParseInt => crate::vm::error::Error::ParseInt, + ErrorType::UserDataArgsEmpty => { + crate::vm::error::Error::UserData(crate::vm::userdata::Error::ArgsEmpty) + } + ErrorType::UserDataMutViolation => crate::vm::error::Error::UserData( + crate::vm::userdata::Error::MutViolation(CStr::from_ptr(err.static_string.data)), + ), + ErrorType::UserDataGc => crate::vm::error::Error::UserData(crate::vm::userdata::Error::Gc), + ErrorType::UserDataIndex => { + crate::vm::error::Error::UserData(crate::vm::userdata::Error::Index) + } + ErrorType::UserDataMetatable => { + crate::vm::error::Error::UserData(crate::vm::userdata::Error::Metatable) + } + ErrorType::UserDataMultiValueField => { + crate::vm::error::Error::UserData(crate::vm::userdata::Error::MultiValueField) + } + ErrorType::UserDataAlreadyRegistered => crate::vm::error::Error::UserData( + crate::vm::userdata::Error::AlreadyRegistered(CStr::from_ptr(err.static_string.data)), + ), + ErrorType::UserDataAlignment => crate::vm::error::Error::UserData( + crate::vm::userdata::Error::Alignment(err.alignment.alignment), + ), + ErrorType::None => std::hint::unreachable_unchecked(), + } +} + +pub struct ModuleManager { + set: HashSet, + loader: ModuleLoader, +} + +impl ModuleManager { + fn load_plugin( + vm: &Vm, + module: &Module, + name: &str, + lib: &str, + plugin: &str, + ) -> Result<()> { + let func_name = format!("bp3d_lua_{}_register_{}", lib, plugin); + let sym = unsafe { module.lib().load_symbol::(func_name) }? + .ok_or_else(|| Error::PluginNotFound(name.into()))?; + let mut err = crate::module::error::Error { + ty: ErrorType::None, + }; + if !sym.call(vm.as_ptr(), &mut err) { + return Err(Error::Vm(unsafe { convert_module_error_to_vm_error(err) })); + } + Ok(()) + } + + fn load_dynamic(&mut self, lib: &str, plugin: &str, vm: &Vm) -> Result<()> { + let name = format!("{}::{}", lib, plugin); + if self.set.contains(&name) { + return Err(Error::PluginAlreadyLoaded(name)); + } + let module = unsafe { self.loader.load(lib) }?; + info!( + "Loaded dynamic module {:?}-{:?}", + module.get_metadata_key("NAME"), + module.get_metadata_key("VERSION") + ); + Self::load_plugin(vm, module, &name, &lib.replace("-", "_"), plugin)?; + info!("Loaded plugin {}", name); + self.set.insert(name); + Ok(()) + } + + fn load_builtin(&mut self, lib: &str, plugin: &str, vm: &Vm) -> Result { + let name = format!("{}::{}", lib, plugin); + if self.set.contains(&name) { + return Err(Error::PluginAlreadyLoaded(name)); + } + let module = match unsafe { self.loader.load_builtin(lib) } { + Ok(v) => v, + Err(e) => { + return match e { + bp3d_os::module::error::Error::NotFound(_) => Ok(false), + e => Err(Error::Module(e)), + } + } + }; + info!( + "Loaded builtin module {:?}-{:?}", + module.get_metadata_key("NAME"), + module.get_metadata_key("VERSION") + ); + Self::load_plugin(vm, module, &name, &lib.replace("-", "_"), plugin)?; + info!("Loaded plugin {}", name); + self.set.insert(name); + Ok(true) + } + + pub fn load(&mut self, lib: &str, plugin: &str, vm: &Vm) -> Result<()> { + if !self.load_builtin(lib, plugin, vm)? { + self.load_dynamic(lib, plugin, vm)?; + } + Ok(()) + } + + pub fn add_search_path(&mut self, name: PathBuf) { + self.loader.add_search_path(name) + } + + pub fn new(builtins: &'static [&'static VirtualLibrary]) -> Self { + let mut loader = ModuleLoader::new(builtins); + loader.add_public_dependency("bp3d-lua", VERSION); + loader.add_public_dependency("time", TIME_VERSION); + Self { + set: Default::default(), + loader, + } + } +} + +impl Default for ModuleManager { + fn default() -> Self { + Self::new(&[]) + } +} diff --git a/core/src/util/namespace.rs b/core/src/util/namespace.rs new file mode 100644 index 0000000..80322e9 --- /dev/null +++ b/core/src/util/namespace.rs @@ -0,0 +1,101 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::lua_settop; +use crate::vm::registry::core::Key; +use crate::vm::table::Table; +use crate::vm::value::IntoLua; +use crate::vm::Vm; + +pub struct Namespace<'a> { + vm: &'a Vm, + table: Table<'a>, +} + +impl<'a> Namespace<'a> { + fn from_table<'b>( + vm: &'a Vm, + table: Table<'a>, + names: impl Iterator, + ) -> crate::vm::Result { + let key = Key::::new(table); + let key = vm.scope(|vm| { + for name in names { + let mut table = key.push(vm); + let tbl: Option
= table.get(name)?; + let tab = match tbl { + Some(v) => v, + None => { + table.set(name, Table::new(vm))?; + table.get(name)? + } + }; + key.set(tab); + } + Ok(key) + })?; + let table = key.push(vm); + key.delete(vm); + Ok(Self { vm, table }) + } + + pub fn new(vm: &'a Vm, path: &str) -> crate::vm::Result { + let mut names = path.split("."); + let name = names.next().expect("Attempt to build an empty namespace"); + let value: Option> = vm.get_global(name)?; + let table = match value { + Some(table) => table, + None => { + vm.set_global(name, Table::new(vm))?; + vm.get_global(name)? + } + }; + Self::from_table(vm, table, names) + } + + pub fn add<'b, T: IntoLua>( + &mut self, + items: impl IntoIterator, + ) -> crate::vm::Result<()> { + for (name, item) in items { + self.table.set(name, item)?; + } + Ok(()) + } + + pub fn vm(&self) -> &'a Vm { + self.vm + } +} + +impl Drop for Namespace<'_> { + fn drop(&mut self) { + // Clear the table which should be on top of the stack. + unsafe { lua_settop(self.vm.as_ptr(), -2) }; + } +} diff --git a/core/src/vm/closure/context.rs b/core/src/vm/closure/context.rs new file mode 100644 index 0000000..cc92c0b --- /dev/null +++ b/core/src/vm/closure/context.rs @@ -0,0 +1,218 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Second version of the context tool. + +use crate::ffi::laux::luaL_error; +use crate::ffi::lua::lua_newuserdata; +use crate::util::core::SimpleDrop; +use crate::vm::closure::{FromUpvalue, IntoUpvalue, Upvalue}; +use crate::vm::registry::core::RawKey; +use crate::vm::Vm; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; + +pub struct Cell { + ptr: *mut *const T, +} + +impl Cell { + pub fn new(ctx: Context) -> Self { + Self { ptr: ctx.ptr } + } + + pub fn bind<'a>(&mut self, obj: &'a T) -> Guard<'a, T> { + unsafe { *self.ptr = obj as _ }; + Guard { + useless: PhantomData, + ud: self.ptr, + } + } +} + +pub struct CellMut { + ptr: *mut *const T, +} + +impl CellMut { + pub fn new(ctx: ContextMut) -> Self { + Self { ptr: ctx.0.ptr } + } + + pub fn bind<'a>(&mut self, obj: &'a mut T) -> Guard<'a, T> { + unsafe { *self.ptr = obj as _ }; + Guard { + useless: PhantomData, + ud: self.ptr, + } + } +} + +pub struct Context { + key: RawKey, + ptr: *mut *const T, +} + +impl Clone for Context { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Context {} + +pub struct ContextMut(Context); + +impl Clone for ContextMut { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for ContextMut {} + +impl Context { + pub fn new(vm: &Vm) -> Self { + let (ptr, key) = unsafe { + let ptr = lua_newuserdata(vm.as_ptr(), 8); + std::ptr::write(ptr as *mut u64, 0); + (ptr, RawKey::from_top(vm)) + }; + Self { + key, + ptr: ptr as *mut *const T, + } + } +} + +impl ContextMut { + pub fn new(vm: &Vm) -> Self { + Self(Context::new(vm)) + } +} + +#[repr(transparent)] +pub struct Guard<'a, T> { + ud: *mut *const T, + useless: PhantomData<&'a T>, +} + +impl Drop for Guard<'_, T> { + #[inline(always)] + fn drop(&mut self) { + unsafe { + *self.ud = std::ptr::null(); + } + } +} + +#[repr(transparent)] +pub struct Ref<'a, T>(&'a T); + +#[repr(transparent)] +pub struct Mut<'a, T>(&'a mut T); + +impl Deref for Ref<'_, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl Deref for Mut<'_, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl DerefMut for Mut<'_, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +unsafe impl SimpleDrop for Ref<'_, T> {} +unsafe impl SimpleDrop for Mut<'_, T> {} + +impl<'a, T: 'static> FromUpvalue<'a> for Ref<'a, T> { + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + let ptr: *mut *const T = FromUpvalue::from_upvalue(vm, index); + if (*ptr).is_null() { + luaL_error( + vm.as_ptr(), + c"Context is not available in this function.".as_ptr(), + ); + // luaL_error raises a lua exception and unwinds, so this cannot be reached. + std::hint::unreachable_unchecked(); + } + Ref(unsafe { &**ptr }) + } +} + +impl<'a, T: 'static> FromUpvalue<'a> for Mut<'a, T> { + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + let ptr: *mut *mut T = FromUpvalue::from_upvalue(vm, index); + if (*ptr).is_null() { + luaL_error( + vm.as_ptr(), + c"Context is not available in this function.".as_ptr(), + ); + // luaL_error raises a lua exception and unwinds, so this cannot be reached. + std::hint::unreachable_unchecked(); + } + Mut(unsafe { &mut **ptr }) + } +} + +impl IntoUpvalue for Context { + fn into_upvalue(self, vm: &Vm) -> u16 { + unsafe { self.key.push(vm) }; + 1 + } +} + +impl IntoUpvalue for ContextMut { + fn into_upvalue(self, vm: &Vm) -> u16 { + unsafe { self.0.key.push(vm) }; + 1 + } +} + +impl Upvalue for Context { + type From<'a> = Ref<'a, T>; +} + +impl Upvalue for ContextMut { + type From<'a> = Mut<'a, T>; +} diff --git a/core/src/vm/closure/core.rs b/core/src/vm/closure/core.rs new file mode 100644 index 0000000..e23e25f --- /dev/null +++ b/core/src/vm/closure/core.rs @@ -0,0 +1,160 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_pushlightuserdata, lua_topointer, GLOBALSINDEX}; +use crate::vm::closure::{FromUpvalue, IntoUpvalue, Upvalue}; +use crate::vm::function::IntoParam; +use crate::vm::value::FromLua; +use crate::vm::Vm; +use std::ffi::OsStr; +use std::path::Path; + +macro_rules! impl_from_upvalue_using_from_lua_unchecked { + ($($t: ty),*) => { + $( + impl FromUpvalue<'_> for $t { + #[inline(always)] + unsafe fn from_upvalue(vm: &Vm, index: i32) -> Self { + <$t>::from_lua_unchecked(vm, GLOBALSINDEX - index) + } + } + + impl Upvalue for $t { + type From<'a> = $t; + } + )* + }; +} + +impl<'a> FromUpvalue<'a> for &'a str { + #[inline(always)] + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + FromLua::from_lua_unchecked(vm, GLOBALSINDEX - index) + } +} + +impl Upvalue for &str { + type From<'a> = &'a str; +} + +impl<'a> FromUpvalue<'a> for &'a [u8] { + #[inline(always)] + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + FromLua::from_lua_unchecked(vm, GLOBALSINDEX - index) + } +} + +impl Upvalue for &[u8] { + type From<'a> = &'a [u8]; +} + +#[cfg(target_pointer_width = "64")] +impl_from_upvalue_using_from_lua_unchecked!(i64, u64); + +impl_from_upvalue_using_from_lua_unchecked!(i8, u8, i16, u16, i32, u32, f32, f64, bool); + +impl FromUpvalue<'_> for *mut T { + #[inline(always)] + unsafe fn from_upvalue(vm: &Vm, index: i32) -> Self { + lua_topointer(vm.as_ptr(), GLOBALSINDEX - index) as _ + } +} + +impl FromUpvalue<'_> for *const T { + #[inline(always)] + unsafe fn from_upvalue(vm: &'_ Vm, index: i32) -> Self { + lua_topointer(vm.as_ptr(), GLOBALSINDEX - index) as _ + } +} + +impl IntoUpvalue for T { + #[inline(always)] + fn into_upvalue(self, vm: &Vm) -> u16 { + self.into_param(vm) + } +} + +impl IntoUpvalue for *mut T { + fn into_upvalue(self, vm: &Vm) -> u16 { + unsafe { lua_pushlightuserdata(vm.as_ptr(), self as _) }; + 1 + } +} + +impl IntoUpvalue for *const T { + fn into_upvalue(self, vm: &Vm) -> u16 { + unsafe { lua_pushlightuserdata(vm.as_ptr(), self as _) }; + 1 + } +} + +impl Upvalue for *mut T { + type From<'a> = *mut T; +} + +impl Upvalue for *const T { + type From<'a> = *const T; +} + +impl<'a> FromUpvalue<'a> for &'a OsStr { + #[inline(always)] + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + OsStr::from_encoded_bytes_unchecked(FromUpvalue::from_upvalue(vm, index)) + } +} + +impl IntoUpvalue for &OsStr { + #[inline(always)] + fn into_upvalue(self, vm: &Vm) -> u16 { + self.as_encoded_bytes().into_upvalue(vm) + } +} + +impl Upvalue for &OsStr { + type From<'a> = &'a OsStr; +} + +impl<'a> FromUpvalue<'a> for &'a Path { + #[inline(always)] + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + Path::new(OsStr::from_encoded_bytes_unchecked( + FromUpvalue::from_upvalue(vm, index), + )) + } +} + +impl IntoUpvalue for &Path { + #[inline(always)] + fn into_upvalue(self, vm: &Vm) -> u16 { + self.as_os_str().into_upvalue(vm) + } +} + +impl Upvalue for &Path { + type From<'a> = &'a Path; +} diff --git a/core/src/vm/closure/interface.rs b/core/src/vm/closure/interface.rs new file mode 100644 index 0000000..b68a477 --- /dev/null +++ b/core/src/vm/closure/interface.rs @@ -0,0 +1,57 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::util::core::SimpleDrop; +use crate::vm::Vm; + +/// This trait represents a closure parameter. +pub trait FromUpvalue<'a>: Sized + SimpleDrop { + /// Reads this value from the given lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to read from. + /// * `index`: index of the parameter to read. + /// + /// returns: Self + /// + /// # Safety + /// + /// Calling this function outside the body of a [CFunction](crate::ffi::lua::CFunction) is UB. + /// Calling this function in a non-POF segment of that CFunction is also UB. Finally, if the + /// type of the value at index `index` is not of [Self], calling this function is UB. + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self; +} + +pub trait IntoUpvalue: Upvalue { + fn into_upvalue(self, vm: &Vm) -> u16; +} + +pub trait Upvalue { + type From<'a>: FromUpvalue<'a>; +} diff --git a/core/src/vm/closure/mod.rs b/core/src/vm/closure/mod.rs new file mode 100644 index 0000000..1d3cb24 --- /dev/null +++ b/core/src/vm/closure/mod.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod context; +mod core; +mod interface; +pub mod rc; +pub mod types; + +pub use interface::*; diff --git a/core/src/vm/closure/rc.rs b/core/src/vm/closure/rc.rs new file mode 100644 index 0000000..4592e0a --- /dev/null +++ b/core/src/vm/closure/rc.rs @@ -0,0 +1,74 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::util::core::SimpleDrop; +use crate::vm::closure::{FromUpvalue, IntoUpvalue, Upvalue}; +use crate::vm::Vm; +use std::ops::Deref; + +#[repr(transparent)] +pub struct Rc(*const T); + +#[repr(transparent)] +pub struct Ref<'a, T>(&'a T); + +unsafe impl SimpleDrop for Ref<'_, T> {} + +impl Deref for Ref<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a, T> FromUpvalue<'a> for Ref<'a, T> { + #[inline(always)] + unsafe fn from_upvalue(vm: &'a Vm, index: i32) -> Self { + let ptr: *const T = FromUpvalue::from_upvalue(vm, index); + Ref(&*ptr) + } +} + +impl Upvalue for Rc { + type From<'a> = Ref<'a, T>; +} + +impl IntoUpvalue for Rc { + #[inline(always)] + fn into_upvalue(self, vm: &Vm) -> u16 { + self.0.into_upvalue(vm) + } +} + +impl Rc { + #[inline(always)] + pub fn from_rust(vm: &Vm, rc: std::rc::Rc) -> Rc { + Rc(crate::vm::core::destructor::Pool::attach(vm, rc)) + } +} diff --git a/core/src/vm/closure/types.rs b/core/src/vm/closure/types.rs new file mode 100644 index 0000000..9a4fe52 --- /dev/null +++ b/core/src/vm/closure/types.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_pushcclosure, CFunction, State}; +use crate::vm::closure::{FromUpvalue, IntoUpvalue}; +use crate::vm::function::{FromParam, IntoParam}; +use crate::vm::value::IntoLua; +use crate::vm::Vm; +use std::ffi::c_void; + +pub struct RClosure { + func: CFunction, + upvalue: T, +} + +impl RClosure { + /// Creates a new [RClosure]. + /// + /// # Arguments + /// + /// * `func`: the [CFunction] to be associated with an upvalue. + /// * `upvalue`: the upvalue to bind to the [CFunction]. + /// + /// returns: RClosure + pub fn new(func: CFunction, upvalue: T) -> Self { + Self { func, upvalue } + } +} + +unsafe impl IntoLua for RClosure { + fn into_lua(self, vm: &Vm) -> u16 { + let num = self.upvalue.into_upvalue(vm); + unsafe { lua_pushcclosure(vm.as_ptr(), self.func, num as _) }; + 1 + } +} + +impl RClosure<*const c_void> { + pub fn from_rust R + 'static>(vm: &Vm, fun: F) -> Self + where + for<'a> T: FromParam<'a>, + R: IntoParam, + { + let ptr = crate::vm::core::destructor::Pool::attach(vm, Box::new(fun)); + extern "C-unwind" fn _cfunc R>(l: State) -> i32 + where + for<'a> T: FromParam<'a>, + R: IntoParam, + { + let vm = unsafe { Vm::from_raw(l) }; + let upvalue: *const F = unsafe { FromUpvalue::from_upvalue(&vm, 1) }; + let args: T = unsafe { FromParam::from_param(&vm, 1) }; + let res = unsafe { (*upvalue)(args) }; + res.into_param(&vm) as _ + } + RClosure::new(_cfunc::, ptr as *const _) + } +} diff --git a/core/src/vm/core/destructor.rs b/core/src/vm/core/destructor.rs new file mode 100644 index 0000000..ad0e990 --- /dev/null +++ b/core/src/vm/core/destructor.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_pushlightuserdata, lua_settop, lua_touserdata}; +use crate::vm::registry::named::RawKey; +use crate::vm::registry::Set; +use crate::vm::Vm; +use bp3d_debug::debug; +use std::rc::Rc; + +/// This trait represents a value which can be attached to a [Pool](Pool). +pub trait Raw { + type Ptr: Copy; + + fn into_raw(self) -> Self::Ptr; + + /// Deletes the raw pointer. + /// + /// # Safety + /// + /// This function must be called with the same pointer that originated from the same type using + /// the [into_raw](Raw::into_raw) method. + unsafe fn delete(ptr: Self::Ptr); +} + +impl Raw for Box { + type Ptr = *mut T; + + fn into_raw(self) -> Self::Ptr { + Box::into_raw(self) + } + + unsafe fn delete(ptr: Self::Ptr) { + drop(Box::from_raw(ptr)) + } +} + +impl Raw for Rc { + type Ptr = *const T; + + fn into_raw(self) -> Self::Ptr { + Rc::into_raw(self) + } + + unsafe fn delete(ptr: Self::Ptr) { + drop(Rc::from_raw(ptr)) + } +} + +const DESTRUCTOR_POOL: RawKey = RawKey::new("__destructor_pool__"); + +#[derive(Default)] +pub struct Pool { + leaked: Vec>, +} + +impl Pool { + pub fn new() -> Self { + Self::default() + } + + /// Inserts this pool in the given Vm. + /// + /// # Safety + /// + /// This is only safe to be called on [RootVm](crate::vm::RootVm) construction. + pub unsafe fn new_in_vm(vm: &mut Vm) { + DESTRUCTOR_POOL.register(vm); + let l = vm.as_ptr(); + let b = Box::leak(Box::new(Pool::new())); + let ptr = b as *mut Pool as _; + lua_pushlightuserdata(l, ptr); + DESTRUCTOR_POOL.set(vm, -1); + } + + /// Extracts a destructor pool from the given [Vm]. + /// + /// # Safety + /// + /// The returned reference must not be aliased. + unsafe fn _from_vm(vm: &Vm) -> *mut Self { + let l = vm.as_ptr(); + DESTRUCTOR_POOL.push(vm); + let ptr = lua_touserdata(l, -1) as *mut Pool; + assert!(!ptr.is_null()); + lua_settop(l, -2); // Remove the pointer from the lua stack. + ptr + } + + pub fn from_vm(vm: &mut Vm) -> &mut Self { + unsafe { &mut *Self::_from_vm(vm) } + } + + pub fn attach(vm: &Vm, raw: R) -> R::Ptr + where + R::Ptr: 'static, + { + let ptr = unsafe { Self::_from_vm(vm) }; + unsafe { (*ptr).attach_mut(raw) } + } + + pub fn attach_mut(&mut self, raw: R) -> R::Ptr + where + R::Ptr: 'static, + { + let ptr = R::into_raw(raw); + self.leaked.push(Box::new(move || { + unsafe { R::delete(ptr) }; + })); + ptr + } +} + +impl Drop for Pool { + fn drop(&mut self) { + debug!({ num = self.leaked.len() }, "Deleting leaked pointers..."); + let v = std::mem::take(&mut self.leaked); + for f in v { + f() + } + } +} diff --git a/core/src/vm/core/interface.rs b/core/src/vm/core/interface.rs new file mode 100644 index 0000000..1580fe2 --- /dev/null +++ b/core/src/vm/core/interface.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{State, ThreadStatus}; + +pub trait LoadString { + fn load_string(&self, l: State) -> ThreadStatus; +} + +pub trait Load { + fn load(self, l: State) -> ThreadStatus; +} diff --git a/core/src/vm/core/interrupt/mod.rs b/core/src/vm/core/interrupt/mod.rs new file mode 100644 index 0000000..228c925 --- /dev/null +++ b/core/src/vm/core/interrupt/mod.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! This module contains tools to allow interrupting a root Vm. + +#[cfg(unix)] +mod unix; + +#[cfg(windows)] +mod windows; + +#[cfg(unix)] +pub use unix::Signal; + +#[cfg(windows)] +pub use windows::Signal; + +unsafe impl Send for Signal {} +unsafe impl Sync for Signal {} + +use bp3d_util::simple_error; +use std::thread::JoinHandle; + +simple_error! { + pub Error { + AlreadyInterrupting => "attempt to interrupt a Vm while interrupting a different Vm", + IncorrectThread => "attempt to interrupt a Vm from the wrong thread", + Timeout => "the lua hook did not trigger in the requested time (is the JIT enabled?)", + Unknown => "unknown system error" + } +} + +pub fn spawn_interruptible( + f: impl FnOnce(&mut crate::vm::RootVm) -> R + Send + 'static, +) -> (Signal, JoinHandle) { + let (send, recv) = std::sync::mpsc::channel(); + let handle = std::thread::spawn(move || { + let mut vm = crate::vm::RootVm::new(); + send.send(Signal::create(&mut vm)).unwrap(); + f(&mut vm) + }); + (recv.recv().unwrap(), handle) +} diff --git a/core/src/vm/core/interrupt/unix.rs b/core/src/vm/core/interrupt/unix.rs new file mode 100644 index 0000000..9e2c7c0 --- /dev/null +++ b/core/src/vm/core/interrupt/unix.rs @@ -0,0 +1,150 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::ext::lua_ext_ccatch_error; +use crate::ffi::lua::{ + lua_pushstring, lua_sethook, Debug, State, MASKCALL, MASKCOUNT, MASKLINE, MASKRET, +}; +use crate::vm::core::interrupt::Error; +use crate::vm::RootVm; +use bp3d_debug::{error, warning}; +use libc::{c_int, pthread_kill, pthread_self, pthread_t, SIGUSR1}; +use std::mem::MaybeUninit; +use std::sync::{Mutex, Once}; +use std::thread::ThreadId; +use std::time::Duration; + +pub struct Signal { + l: State, + thread: ThreadId, + th: pthread_t, +} + +struct SigState { + l: State, + thread: ThreadId, + return_chan: std::sync::mpsc::Sender>, + notify_chan: std::sync::mpsc::Sender<()>, +} + +unsafe impl Send for SigState {} + +static SIG_STATE: Mutex> = Mutex::new(None); + +extern "C-unwind" fn lua_interrupt(l: State, _: Debug) { + { + let mut state = SIG_STATE.lock().unwrap(); + if let Some(sig) = state.take() { + if let Err(e) = sig.notify_chan.send(()) { + error!({error=?e}, "Failed to notify interrupt signal") + } + } + } + unsafe { + lua_sethook(l, None, 0, 0); + lua_pushstring(l, c"interrupted".as_ptr()); + lua_ext_ccatch_error(l); + } +} + +extern "C" fn signal_handler(_: c_int) { + let res = SIG_STATE.try_lock(); + match res { + Ok(v) => { + if let Some(v) = &*v { + let current_id = std::thread::current().id(); + if current_id != v.thread { + v.return_chan.send(Err(Error::IncorrectThread)).unwrap(); + return; + } + // Run the hook 1 instruction later. + unsafe { + lua_sethook( + v.l, + Some(lua_interrupt), + MASKCOUNT | MASKCALL | MASKLINE | MASKRET, + 1, + ) + }; + v.return_chan.send(Ok(())).unwrap(); + } + } + Err(e) => { + error!({error=?e}, "Attempt to interrupt a Vm while interrupting a different Vm"); + } + } +} + +static SIG_BOUND: Once = Once::new(); + +impl Signal { + pub fn create(vm: &mut RootVm) -> Self { + let th = unsafe { pthread_self() }; + let l = vm.as_ptr(); + let thread = std::thread::current().id(); + SIG_BOUND.call_once(|| { + let mut sig: libc::sigaction = unsafe { MaybeUninit::zeroed().assume_init() }; + sig.sa_sigaction = signal_handler as _; + let ret = unsafe { libc::sigaction(SIGUSR1, &sig as _, std::ptr::null_mut()) }; + assert_eq!(ret, 0); + }); + Self { l, thread, th } + } + + pub fn send(&self, duration: Duration) -> Result<(), Error> { + let (send, recv) = std::sync::mpsc::channel(); + let (send2, recv2) = std::sync::mpsc::channel(); + { + let mut lock = SIG_STATE + .try_lock() + .map_err(|_| Error::AlreadyInterrupting)?; + *lock = Some(SigState { + l: self.l, + thread: self.thread, + return_chan: send, + notify_chan: send2, + }); + } + let ret = unsafe { pthread_kill(self.th, SIGUSR1) }; + if ret != 0 { + return Err(Error::Unknown); + } + recv.recv().unwrap()?; + match recv2.recv_timeout(duration) { + Ok(()) => Ok(()), + Err(e) => { + warning!({error=?e}, "Error attempting to wait for interrupt notification"); + { + let mut guard = SIG_STATE.lock().unwrap(); + *guard = None; + } + Err(Error::Timeout) + } + } + } +} diff --git a/core/src/vm/core/interrupt/windows.rs b/core/src/vm/core/interrupt/windows.rs new file mode 100644 index 0000000..a906e1e --- /dev/null +++ b/core/src/vm/core/interrupt/windows.rs @@ -0,0 +1,124 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use super::Error; +use crate::ffi::ext::lua_ext_ccatch_error; +use crate::ffi::lua::{ + lua_pushstring, lua_sethook, Debug, State, MASKCALL, MASKCOUNT, MASKLINE, MASKRET, +}; +use crate::vm::RootVm; +use bp3d_debug::{error, warning}; +use std::sync::Mutex; +use std::time::Duration; +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::System::Diagnostics::Debug::{GetThreadContext, CONTEXT}; +use windows_sys::Win32::System::Threading::{GetCurrentThread, ResumeThread, SuspendThread}; + +static SIG_STATE: Mutex>> = Mutex::new(None); + +extern "C-unwind" fn lua_interrupt(l: State, _: Debug) { + { + let mut state = SIG_STATE.lock().unwrap(); + if let Some(sig) = state.take() { + if let Err(e) = sig.send(()) { + error!({error=?e}, "Failed to notify interrupt signal") + } + } + } + unsafe { + lua_sethook(l, None, 0, 0); + lua_pushstring(l, c"interrupted".as_ptr()); + lua_ext_ccatch_error(l); + } +} + +pub struct Signal { + l: State, + th: HANDLE, +} + +impl Signal { + pub fn create(vm: &mut RootVm) -> Self { + let th = unsafe { GetCurrentThread() }; + let l = vm.as_ptr(); + Self { l, th } + } + + pub fn send(&self, duration: Duration) -> Result<(), Error> { + let (send2, recv2) = std::sync::mpsc::channel(); + { + let mut lock = SIG_STATE + .try_lock() + .map_err(|_| Error::AlreadyInterrupting)?; + *lock = Some(send2); + } + if self.th == unsafe { GetCurrentThread() } { + // If somehow the system thread that ineterrupts the Vm is the same as the one which started the Vm, then directly set the hook. + unsafe { + lua_sethook( + self.l, + Some(lua_interrupt), + MASKCOUNT | MASKCALL | MASKLINE | MASKRET, + 1, + ); + } + } else { + unsafe { + let mut ctx: CONTEXT = std::mem::zeroed(); + // Requests to suspend the thread. + if SuspendThread(self.th) == u32::MAX { + //(DWORD) -1 + return Err(Error::Unknown); + } + // This call forces synchronization with the thread to be suspended. + if GetThreadContext(self.th, &mut ctx as _) == 0 { + return Err(Error::Unknown); + } + lua_sethook( + self.l, + Some(lua_interrupt), + MASKCOUNT | MASKCALL | MASKLINE | MASKRET, + 1, + ); + // Resume the thread. + let _ = ResumeThread(self.th); + } + } + match recv2.recv_timeout(duration) { + Ok(()) => Ok(()), + Err(e) => { + warning!({error=?e}, "Error attempting to wait for interrupt notification"); + { + let mut guard = SIG_STATE.lock().unwrap(); + *guard = None; + } + Err(Error::Timeout) + } + } + } +} diff --git a/core/src/vm/core/iter.rs b/core/src/vm/core/iter.rs new file mode 100644 index 0000000..ac24255 --- /dev/null +++ b/core/src/vm/core/iter.rs @@ -0,0 +1,58 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::vm::value::FromLua; +use crate::vm::Vm; +use std::marker::PhantomData; + +pub struct Iter<'a, T> { + vm: &'a Vm, + index: i32, + useless: PhantomData, +} + +impl<'a, T: FromLua<'a>> Iterator for Iter<'a, T> { + type Item = crate::vm::Result; + + fn next(&mut self) -> Option { + if self.index > self.vm.top() { + return None; + } + let item = T::from_lua(self.vm, self.index); + self.index += 1; + Some(item) + } +} + +pub fn start<'a, T: FromLua<'a>>(vm: &'a Vm, start_index: i32) -> Iter<'a, T> { + Iter { + vm, + index: start_index, + useless: PhantomData, + } +} diff --git a/core/src/vm/core/jit.rs b/core/src/vm/core/jit.rs new file mode 100644 index 0000000..4761d5b --- /dev/null +++ b/core/src/vm/core/jit.rs @@ -0,0 +1,315 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::ext::{lua_ext_getjitflags, lua_ext_setjitflags, lua_ext_setjitmode}; +use crate::ffi::jit; +use crate::vm::{RootVm, Vm}; +use std::ffi::c_int; +use std::fmt::{Display, Formatter}; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct CpuArm { + pub v6: bool, + pub v6t2: bool, + pub v7: bool, + pub v8: bool, + pub vfpv2: bool, + pub vfpv3: bool, +} + +impl CpuArm { + pub fn has_vfp(&self) -> bool { + self.vfpv2 || self.vfpv3 + } +} + +impl Display for CpuArm { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if !self.v6 && !self.v7 && !self.v8 && !self.v6t2 && !self.vfpv2 && !self.vfpv3 { + write!(f, "ARM CPU, no features")?; + } else { + write!(f, "ARM CPU, features:")?; + if self.v6 { + write!(f, " ARMV6")?; + } + if self.v6t2 { + write!(f, " ARMV6T2")?; + } + if self.v7 { + write!(f, " ARMV7")?; + } + if self.v8 { + write!(f, " ARMV8")?; + } + if self.vfpv2 { + write!(f, " VFPV2")?; + } + if self.vfpv3 { + write!(f, " VFPV3")?; + } + } + Ok(()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct CpuX86 { + pub sse3: bool, + pub sse4_1: bool, + pub bmi2: bool, +} + +impl Display for CpuX86 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if !self.sse3 && !self.sse4_1 && !self.bmi2 { + write!(f, "X86 CPU, no features")?; + } else { + write!(f, "X86 CPU, features:")?; + if self.sse3 { + write!(f, " SSE3")?; + } + if self.sse4_1 { + write!(f, " SSE4_1")?; + } + if self.bmi2 { + write!(f, " BMI2")?; + } + } + Ok(()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Cpu { + X86(CpuX86), + Arm(CpuArm), +} + +impl Display for Cpu { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Cpu::X86(cpu) => cpu.fmt(f), + Cpu::Arm(cpu) => cpu.fmt(f), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct Opts { + pub fold: bool, + pub cse: bool, + pub dce: bool, + pub fwd: bool, + pub dse: bool, + pub narrow: bool, + pub loop1: bool, + pub abc: bool, + pub sink: bool, + pub fuse: bool, + pub fma: bool, +} + +impl Display for Opts { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "optimizations:")?; + if self.fold { + write!(f, " fold")?; + } + if self.cse { + write!(f, " cse")?; + } + if self.dce { + write!(f, " dce")?; + } + if self.fwd { + write!(f, " fwd")?; + } + if self.dse { + write!(f, " dse")?; + } + if self.narrow { + write!(f, " narrow")?; + } + if self.loop1 { + write!(f, " loop")?; + } + if self.abc { + write!(f, " abc")?; + } + if self.sink { + write!(f, " sink")?; + } + if self.fuse { + write!(f, " fuse")?; + } + if self.fma { + write!(f, " fma")?; + } + Ok(()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub enum OptLevel { + O0, + O1, + O2, + #[default] + O3, + Unknown, +} + +impl Display for OptLevel { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + OptLevel::O0 => write!(f, "O0"), + OptLevel::O1 => write!(f, "O1"), + OptLevel::O2 => write!(f, "O2"), + OptLevel::O3 => write!(f, "O3"), + OptLevel::Unknown => write!(f, "Unknown"), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct JitOptions { + cur_flag_set: u32, + mode: c_int, + opt_level_changed: bool, +} + +impl JitOptions { + /// Read JIT options for the given [Vm](Vm). + /// + /// # Arguments + /// + /// * `vm`: the [Vm] instance to read options for. + /// + /// returns: JitOptions + pub fn get(vm: &Vm) -> JitOptions { + Self { + cur_flag_set: unsafe { lua_ext_getjitflags(vm.as_ptr()) }, + mode: -1, + opt_level_changed: false, + } + } + + pub fn is_enabled(&self) -> bool { + (self.cur_flag_set & jit::F_ON) != 0 + } + + pub fn cpu(&self) -> Cpu { + #[cfg(target_arch = "x86_64")] + let cpu = Cpu::X86(CpuX86 { + sse3: (self.cur_flag_set & jit::F_SSE3) != 0, + sse4_1: (self.cur_flag_set & jit::F_SSE4_1) != 0, + bmi2: (self.cur_flag_set & jit::F_BMI2) != 0, + }); + #[cfg(any(target_arch = "aarch64", target_arch = "arm"))] + let cpu = Cpu::Arm(CpuArm { + v6: (self.cur_flag_set & jit::F_ARMV6_) != 0, + v6t2: (self.cur_flag_set & jit::F_ARMV6T2_) != 0, + v7: (self.cur_flag_set & jit::F_ARMV7) != 0, + v8: (self.cur_flag_set & jit::F_ARMV8) != 0, + vfpv2: (self.cur_flag_set & jit::F_VFPV2) != 0, + vfpv3: (self.cur_flag_set & jit::F_VFPV3) != 0, + }); + cpu + } + + pub fn opts(&self) -> Opts { + Opts { + fold: (self.cur_flag_set & jit::F_OPT_FOLD) != 0, + cse: (self.cur_flag_set & jit::F_OPT_CSE) != 0, + dce: (self.cur_flag_set & jit::F_OPT_DCE) != 0, + fwd: (self.cur_flag_set & jit::F_OPT_FWD) != 0, + dse: (self.cur_flag_set & jit::F_OPT_DSE) != 0, + narrow: (self.cur_flag_set & jit::F_OPT_NARROW) != 0, + loop1: (self.cur_flag_set & jit::F_OPT_LOOP) != 0, + abc: (self.cur_flag_set & jit::F_OPT_ABC) != 0, + sink: (self.cur_flag_set & jit::F_OPT_SINK) != 0, + fuse: (self.cur_flag_set & jit::F_OPT_FUSE) != 0, + fma: (self.cur_flag_set & jit::F_OPT_FMA) != 0, + } + } + + pub fn opt_level(&self) -> OptLevel { + if (self.cur_flag_set & jit::F_OPT_3) == jit::F_OPT_3 { + return OptLevel::O3; + } + if (self.cur_flag_set & jit::F_OPT_2) == jit::F_OPT_2 { + return OptLevel::O2; + } + if (self.cur_flag_set & jit::F_OPT_1) == jit::F_OPT_1 { + return OptLevel::O1; + } + if (self.cur_flag_set & jit::F_OPT_MASK) == jit::F_OPT_0 { + return OptLevel::O0; + } + OptLevel::Unknown + } + + pub fn flush(&mut self) { + self.mode = jit::MODE_FLUSH; + } + + pub fn disable(&mut self) { + if self.is_enabled() { + self.mode = jit::MODE_OFF; + } + } + + pub fn enable(&mut self) { + if !self.is_enabled() { + self.mode = jit::MODE_ON; + } + } + + pub fn set_opt_level(&mut self, level: OptLevel) { + self.cur_flag_set &= !jit::F_OPT_MASK; + match level { + OptLevel::O0 | OptLevel::Unknown => self.cur_flag_set |= jit::F_OPT_0, + OptLevel::O1 => self.cur_flag_set |= jit::F_OPT_1, + OptLevel::O2 => self.cur_flag_set |= jit::F_OPT_2, + OptLevel::O3 => self.cur_flag_set |= jit::F_OPT_3, + } + self.opt_level_changed = true; + } + + pub fn apply(self, vm: &mut RootVm) { + if self.mode != -1 { + assert_eq!(unsafe { lua_ext_setjitmode(vm.as_ptr(), self.mode) }, 0); + } + if self.opt_level_changed { + assert_eq!( + unsafe { lua_ext_setjitflags(vm.as_ptr(), self.cur_flag_set) }, + 0 + ); + } + } +} diff --git a/core/src/vm/core/load.rs b/core/src/vm/core/load.rs new file mode 100644 index 0000000..5365c4c --- /dev/null +++ b/core/src/vm/core/load.rs @@ -0,0 +1,186 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::{luaL_loadbuffer, luaL_loadstring}; +use crate::ffi::lua::{lua_load, State, ThreadStatus}; +use crate::vm::core::util::{ChunkName, ChunkNameBuilder}; +use crate::vm::core::{Load, LoadString}; +use crate::vm::util::lua_rust_error; +use std::ffi::{c_char, c_void, CStr, CString, OsStr}; +use std::fmt::Write; +use std::fs::File; +use std::path::Path; + +impl LoadString for &CStr { + #[inline(always)] + fn load_string(&self, l: State) -> ThreadStatus { + unsafe { luaL_loadstring(l, self.as_ptr()) } + } +} + +impl LoadString for &str { + fn load_string(&self, l: State) -> ThreadStatus { + let s = CString::new(*self); + match s { + Ok(v) => (&*v).load_string(l), + Err(_) => ThreadStatus::ErrSyntax, + } + } +} + +pub struct Code<'a> { + name: &'a str, + code: &'a [u8], +} + +impl<'a> Code<'a> { + pub fn new(name: &'a str, code: &'a [u8]) -> Self { + Self { name, code } + } +} + +impl Load for Code<'_> { + fn load(self, l: State) -> ThreadStatus { + let mut builder = ChunkNameBuilder::new(); + let _ = write!(&mut builder, "={}", self.name); + let name = builder.build(); + unsafe { + luaL_loadbuffer( + l, + self.code.as_ptr() as _, + self.code.len(), + name.cstr().as_ptr(), + ) + } + } +} + +impl Load for T { + fn load(self, l: State) -> ThreadStatus { + self.load_string(l) + } +} + +pub trait Custom { + type Error: std::error::Error; + + fn read_data(&mut self) -> Result<&[u8], Self::Error>; +} + +/// Bind a custom Rust loader to Lua. +/// +/// # Safety +/// +/// This is UB to call outside a [Load] trait implementation. +pub unsafe fn load_custom( + l: State, + chunk_name: ChunkName, + mut custom: T, +) -> ThreadStatus { + extern "C-unwind" fn _reader( + l: State, + ud: *mut c_void, + sz: *mut usize, + ) -> *const c_char { + let obj = ud as *mut T; + unsafe { + let res = (*obj).read_data(); + match res { + Err(e) => { + lua_rust_error(l, e); + } + Ok(v) => { + *sz = v.len(); + v.as_ptr() as _ + } + } + } + } + lua_load( + l, + _reader::, + &mut custom as *mut T as _, + chunk_name.cstr().as_ptr(), + ) +} + +const BUF_SIZE: usize = 8192; + +pub struct Read { + inner: T, + buffer: [u8; BUF_SIZE], + len: usize, +} + +impl Read { + pub fn new(inner: T) -> Self { + Self { + inner, + buffer: [0; BUF_SIZE], + len: 0, + } + } +} + +impl Custom for Read { + type Error = std::io::Error; + + fn read_data(&mut self) -> Result<&[u8], Self::Error> { + self.len = self.inner.read(&mut self.buffer[..])?; + Ok(&self.buffer[..self.len]) + } +} + +pub struct Script { + file: File, + chunk_name: ChunkName, +} + +impl Script { + pub fn from_path(path: impl AsRef) -> std::io::Result { + let mut builder = ChunkNameBuilder::new(); + let file_name = path + .as_ref() + .file_name() + .unwrap_or(OsStr::new("unnamed")) + .to_str() + .unwrap_or("not-unicode"); + let _ = write!(&mut builder, "@{}", file_name); + let file = File::open(path)?; + Ok(Self { + file, + chunk_name: builder.build(), + }) + } +} + +impl Load for Script { + fn load(self, l: State) -> ThreadStatus { + unsafe { load_custom(l, self.chunk_name, Read::new(self.file)) } + } +} diff --git a/core/src/vm/core/mod.rs b/core/src/vm/core/mod.rs new file mode 100644 index 0000000..cf328a0 --- /dev/null +++ b/core/src/vm/core/mod.rs @@ -0,0 +1,47 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod destructor; +mod interface; +pub mod iter; +pub mod load; +#[cfg(feature = "root-vm")] +mod root_vm; +pub mod util; +mod vm; + +#[cfg(feature = "root-vm")] +pub mod jit; + +#[cfg(feature = "interrupt")] +pub mod interrupt; + +pub use interface::*; +#[cfg(feature = "root-vm")] +pub use root_vm::RootVm; +pub use vm::Vm; diff --git a/core/src/vm/core/root_vm.rs b/core/src/vm/core/root_vm.rs new file mode 100644 index 0000000..8239a0b --- /dev/null +++ b/core/src/vm/core/root_vm.rs @@ -0,0 +1,96 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::{luaL_newstate, luaL_openlibs}; +use crate::ffi::lua::lua_close; +use crate::vm::core::destructor::Pool; +use crate::vm::Vm; +use bp3d_debug::debug; +use std::cell::Cell; +use std::ops::{Deref, DerefMut}; + +thread_local! { + // WTF?! The compiler should be smart enough to do this on its own! Another compiler defect! + static HAS_VM: Cell = const { Cell::new(false) }; +} + +pub struct RootVm { + vm: Vm, +} + +impl Default for RootVm { + fn default() -> Self { + Self::new() + } +} + +impl RootVm { + pub fn new() -> RootVm { + if HAS_VM.get() { + panic!("A VM already exists for this thread.") + } + let l = unsafe { luaL_newstate() }; + unsafe { luaL_openlibs(l) }; + HAS_VM.set(true); + let mut vm = RootVm { + vm: unsafe { Vm::from_raw(l) }, + }; + unsafe { Pool::new_in_vm(&mut vm) }; + vm + } +} + +impl Deref for RootVm { + type Target = Vm; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.vm + } +} + +impl DerefMut for RootVm { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.vm + } +} + +impl Drop for RootVm { + fn drop(&mut self) { + debug!("Deleting destructor pool"); + unsafe { + drop(Box::from_raw(Pool::from_vm(self))); + } + unsafe { + debug!("Closing Lua VM..."); + lua_close(self.vm.as_ptr()); + } + HAS_VM.set(false); + } +} diff --git a/core/src/vm/core/util.rs b/core/src/vm/core/util.rs new file mode 100644 index 0000000..b61f0b3 --- /dev/null +++ b/core/src/vm/core/util.rs @@ -0,0 +1,217 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::{luaL_callmeta, luaL_traceback}; +use crate::ffi::lua::IDSIZE; +use crate::ffi::lua::{ + lua_gettop, lua_isstring, lua_pcall, lua_pushcclosure, lua_pushlstring, lua_remove, + lua_tolstring, lua_type, State, ThreadStatus, Type, +}; +use crate::vm::error::{Error, RuntimeError}; +use crate::vm::value::FromLua; +use crate::vm::Vm; +use bp3d_util::format::FixedBufStr; +use std::ffi::{c_int, CStr}; + +const TRACEBACK_NONE: &[u8] = b"\n"; +extern "C-unwind" fn error_handler(l: State) -> c_int { + unsafe { + let ty = lua_type(l, 1); + if ty != Type::String { + // Non-string error object? Try metamethod. + if (ty == Type::Nil || ty == Type::None) + || luaL_callmeta(l, 1, c"__tostring".as_ptr()) != 1 + || lua_isstring(l, -1) != 1 + { + // Object does not turn into a string remove it alongside the return value of + // __tostring. + lua_remove(l, 1); + lua_remove(l, 1); + // Push a place-holder string to avoid the rust code from crashing because the stack + // would be empty otherwise. + lua_pushlstring(l, TRACEBACK_NONE.as_ptr() as _, TRACEBACK_NONE.len()); + return 1; + } + // Remove the object from the stack so that error message becomes now index 1. + lua_remove(l, 1); + } + // Call traceback with the actual error message as a string which should push onto the stack + // the stacktrace as a string. + luaL_traceback(l, l, lua_tolstring(l, 1, std::ptr::null_mut()), 1); + // Remove the original error message string from the stack. + lua_remove(l, 1); + 1 + } +} + +/// Pushes the error handler on the Lua stack and return its absolute stack index. +/// +/// The error handler replaces the error message by a full traceback using [luaL_traceback]. +/// +/// # Arguments +/// +/// * `l`: the lua State on which to push the error handler function. +/// +/// returns: i32 +/// +/// # Safety +/// +/// You must ensure that the error handler function is NEVER called outside the context of an error. +#[inline(always)] +pub unsafe fn push_error_handler(l: State) -> c_int { + unsafe { + lua_pushcclosure(l, error_handler, 0); + lua_gettop(l) + } +} + +/// Calls the lua function at the top of the stack in a protected environment. +/// +/// # Arguments +/// +/// * `vm`: the [Vm] instance on which to call the function. +/// * `nargs`: the number of arguments push on top of the stack. +/// * `nreturns`: the number of returns expected from the function call. +/// * `handler_pos`: the absolute position of the handler on the stack. +/// +/// returns: Result<(), Error> +/// +/// # Safety +/// +/// This function shall not be used without [push_error_handler]. This is also UB if `nargs` does +/// not match the count of arguments push on top of the stack. If the error handler is not the first +/// item on the stack, before function and function arguments, this is UB. +pub unsafe fn pcall( + vm: &Vm, + nargs: c_int, + nreturns: c_int, + handler_pos: c_int, +) -> crate::vm::Result<()> { + let l = vm.as_ptr(); + unsafe { + // Call the function created by load_code. + let res = lua_pcall(l, nargs, nreturns, handler_pos); + // At this point the stack should no longer have the function but still has the error + // handler and R::num_values results. + // First remove error handler as we no longer need it. + lua_remove(l, handler_pos); + match res { + ThreadStatus::Ok => Ok(()), + ThreadStatus::ErrRun => { + // We've got a runtime error when executing the function so read the full stack + // trace produced by luaL_traceback and remove it from the stack. + let full_traceback: &str = FromLua::from_lua(vm, -1)?; + lua_remove(l, -1); + Err(Error::Runtime(RuntimeError::new(full_traceback.into()))) + } + ThreadStatus::ErrMem => Err(Error::Memory), + ThreadStatus::ErrErr => Err(Error::Error), + ThreadStatus::ErrCcatch => { + // We've got a runtime error when executing the function so read the full stack + // trace produced by luaL_traceback and remove it from the stack. + let full_traceback: &str = FromLua::from_lua(vm, -1)?; + lua_remove(l, -1); + Err(Error::UncatchableRuntime(RuntimeError::new( + full_traceback.into(), + ))) + } + _ => Err(Error::Unknown), + } + } +} + +/// Handles a syntax error. A syntax error is an error which may occur as part of a lua_load family +/// of functions. +/// +/// # Arguments +/// +/// * `vm`: the [Vm] instance which has produced the syntax error. +/// * `res`: the result of the load family of function. +/// * `handler_pos`: the absolute position of the error handler on the stack. +/// +/// returns: Result<(), Error> +/// +/// # Safety +/// +/// Calling this function with a `handler_pos` which does not correspond to the actual error handler +/// C function is UB. This is also UB if the res is not the result of a load function. +pub unsafe fn handle_syntax_error(vm: &Vm, res: ThreadStatus) -> crate::vm::Result<()> { + match res { + ThreadStatus::Ok => Ok(()), + ThreadStatus::ErrSyntax => { + // If we've got an error, read it and clear the stack. + let str: &str = FromLua::from_lua(vm, -1)?; + unsafe { lua_remove(vm.as_ptr(), -1) }; + Err(Error::Syntax(str.into())) + } + ThreadStatus::ErrRun => { + // If we've got an error, read it and clear the stack. + let str: &str = FromLua::from_lua(vm, -1)?; + unsafe { lua_remove(vm.as_ptr(), -1) }; + Err(Error::Loader(str.into())) + } + ThreadStatus::ErrMem => Err(Error::Memory), + _ => Err(Error::Unknown), + } +} + +#[derive(Default)] +pub struct ChunkNameBuilder { + inner: FixedBufStr<59>, +} + +impl ChunkNameBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn build(self) -> ChunkName { + let mut buf = FixedBufStr::::new(); + unsafe { + buf.write(self.inner.str().as_bytes()); + buf.write(b"\0"); + } + ChunkName { inner: buf } + } +} + +impl std::fmt::Write for ChunkNameBuilder { + fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> { + self.inner.write_str(s) + } +} + +pub struct ChunkName { + inner: FixedBufStr<60>, +} + +impl ChunkName { + pub fn cstr(&self) -> &CStr { + unsafe { CStr::from_bytes_with_nul_unchecked(self.inner.str().as_bytes()) } + } +} diff --git a/core/src/vm/core/vm.rs b/core/src/vm/core/vm.rs new file mode 100644 index 0000000..bc8bdb7 --- /dev/null +++ b/core/src/vm/core/vm.rs @@ -0,0 +1,183 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{ + lua_getfield, lua_gettop, lua_pushnil, lua_remove, lua_setfield, lua_settop, State, + ThreadStatus, GLOBALSINDEX, REGISTRYINDEX, +}; +use crate::util::core::AnyStr; +use crate::vm::core::util::{handle_syntax_error, pcall, push_error_handler}; +use crate::vm::core::{Load, LoadString}; +use crate::vm::error::Error; +use crate::vm::userdata::core::Registry; +use crate::vm::userdata::{NameConvert, UserData}; +use crate::vm::value::types::Function; +use crate::vm::value::{FromLua, IntoLua}; + +#[repr(transparent)] +pub struct Vm { + l: State, +} + +impl Vm { + /// Constructs a [Vm] by wrapping an existing lua [State]. + /// + /// # Arguments + /// + /// * `l`: the lua [State] to wrap. + /// + /// # Safety + /// + /// The given lua [State] must have been created from a [RootVm]. It is considered UB to wrap + /// a lua VM allocated outside bp3d-lua. It is also considered UB to wrap a VM which has been + /// allocated with a different set of patches. + #[inline(always)] + pub unsafe fn from_raw(l: State) -> Self { + Self { l } + } + + pub fn scope crate::vm::Result>( + &self, + f: F, + ) -> crate::vm::Result { + let top = self.top(); + let r = f(self)?; + unsafe { lua_settop(self.l, top) }; + Ok(r) + } + + pub fn register_userdata(&self, case: impl NameConvert) -> crate::vm::Result<()> { + let reg = unsafe { Registry::::new(self, case) }.map_err(Error::UserData)?; + let res = T::register(®).map_err(Error::UserData); + match res { + Ok(_) => Ok(()), + Err(e) => { + unsafe { + lua_pushnil(self.l); + lua_setfield(self.l, REGISTRYINDEX, T::CLASS_NAME.as_ptr()); + } + Err(e) + } + } + } + + /// Returns the absolute stack index for the given index. + #[inline(always)] + pub fn get_absolute_index(&self, index: i32) -> i32 { + if index < 0 { + unsafe { lua_gettop(self.l) + index + 1 } + } else { + index + } + } + + /// Returns the top of the lua stack. + #[inline(always)] + pub fn top(&self) -> i32 { + unsafe { lua_gettop(self.l) } + } + + /// Clears the lua stack. + #[inline(always)] + pub fn clear(&mut self) { + unsafe { + lua_settop(self.l, 0); + } + } + + #[inline(always)] + pub fn as_ptr(&self) -> State { + self.l + } + + pub fn set_global(&self, name: impl AnyStr, value: impl IntoLua) -> crate::vm::Result<()> { + value.into_lua(self); + unsafe { + lua_setfield(self.as_ptr(), GLOBALSINDEX, name.to_str()?.as_ptr()); + } + Ok(()) + } + + pub fn get_global<'a, R: FromLua<'a>>(&'a self, name: impl AnyStr) -> crate::vm::Result { + unsafe { + lua_getfield(self.as_ptr(), GLOBALSINDEX, name.to_str()?.as_ptr()); + } + R::from_lua(self, -1) + } + + pub fn run_code<'a, R: FromLua<'a>>(&'a self, code: impl LoadString) -> crate::vm::Result { + let l = self.as_ptr(); + unsafe { + // Push error handler and the get the stack position of it. + let handler_pos = push_error_handler(l); + // Push the lua code. + let res = code.load_string(l); + if res != ThreadStatus::Ok { + lua_remove(l, handler_pos); + } + handle_syntax_error(self, res)?; + pcall(self, 0, R::num_values() as _, handler_pos)?; + } + // Read and return the result of the function from the stack. + FromLua::from_lua(self, -(R::num_values() as i32)) + } + + pub fn load_code(&self, code: impl LoadString) -> crate::vm::Result { + let l = self.as_ptr(); + unsafe { + // Push the lua code. + let res = code.load_string(l); + handle_syntax_error(self, res)?; + Ok(FromLua::from_lua_unchecked(self, -1)) + } + } + + pub fn run<'a, R: FromLua<'a>>(&'a self, obj: impl Load) -> crate::vm::Result { + let l = self.as_ptr(); + let handler_pos = unsafe { push_error_handler(l) }; + let res = obj.load(l); + unsafe { + if res != ThreadStatus::Ok { + lua_remove(l, handler_pos); + } + handle_syntax_error(self, res)?; + pcall(self, 0, R::num_values() as _, handler_pos)?; + } + // Read and return the result of the function from the stack. + FromLua::from_lua(self, -(R::num_values() as i32)) + } + + pub fn load(&self, obj: impl Load) -> crate::vm::Result { + let l = self.as_ptr(); + let res = obj.load(l); + unsafe { + handle_syntax_error(self, res)?; + Ok(FromLua::from_lua_unchecked(self, -1)) + } + } +} diff --git a/core/src/vm/error.rs b/core/src/vm/error.rs new file mode 100644 index 0000000..97d3660 --- /dev/null +++ b/core/src/vm/error.rs @@ -0,0 +1,153 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::Type; +use bp3d_util::simple_error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Copy, Clone)] +pub struct TypeError { + pub expected: Type, + pub actual: Type, +} + +impl Display for TypeError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "expected {:?}, got {:?}", self.expected, self.actual) + } +} + +#[derive(Debug, Clone)] +pub struct RuntimeError { + traceback: String, + index: usize, +} + +impl RuntimeError { + pub fn new(traceback: String) -> Self { + let id = traceback.find('\n').unwrap(); + Self { + traceback, + index: id, + } + } + + pub fn msg(&self) -> &str { + &self.traceback[..self.index] + } + + pub fn stacktrace(&self) -> &str { + &self.traceback[self.index + 1..] + } + + pub fn backtrace(&self) -> &str { + &self.traceback + } +} + +impl Display for RuntimeError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.msg()) + } +} + +#[derive(Debug, Copy, Clone)] +pub struct Utf8Error { + // A re-usable error type is needed for modules so duplicate the one from std. + pub valid_up_to: usize, + pub error_len: Option, +} + +impl From for Utf8Error { + fn from(value: std::str::Utf8Error) -> Self { + Utf8Error { + valid_up_to: value.valid_up_to(), + error_len: value.error_len().map(|v| v as u8), + } + } +} + +impl Utf8Error { + pub const fn valid_up_to(&self) -> usize { + self.valid_up_to + } + + pub const fn error_len(&self) -> Option { + match self.error_len { + Some(len) => Some(len as usize), + None => None, + } + } +} + +impl Display for Utf8Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if let Some(error_len) = self.error_len { + write!( + f, + "invalid utf-8 sequence of {} bytes from index {}", + error_len, self.valid_up_to + ) + } else { + write!( + f, + "incomplete utf-8 byte sequence from index {}", + self.valid_up_to + ) + } + } +} + +simple_error! { + pub Error { + InvalidUtf8(Utf8Error) => "invalid UTF8 string: {}", + Type(TypeError) => "type error: {}", + Syntax(String) => "syntax error: {}", + Runtime(RuntimeError) => "runtime error: {}", + UncatchableRuntime(RuntimeError) => "uncatchable runtime error: {}", + Memory => "memory allocation error", + Unknown => "unknown error", + Error => "error in error handler", + Null => "string contains a null character", + MultiValue => "only one value is supported by this API", + UserData(crate::vm::userdata::Error) => "userdata: {}", + UnsupportedType(Type) => "unsupported lua type: {:?}", + Loader(String) => "loader error: {}", + ParseFloat => "parse float error", + ParseInt => "parse int error" + } +} + +impl Error { + pub fn into_runtime(self) -> Option { + match self { + Error::Runtime(e) => Some(e), + _ => None, + } + } +} diff --git a/core/src/vm/function/core.rs b/core/src/vm/function/core.rs new file mode 100644 index 0000000..c1e47cd --- /dev/null +++ b/core/src/vm/function/core.rs @@ -0,0 +1,327 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::ext::{lua_ext_fast_checkinteger, lua_ext_fast_checknumber}; +use crate::ffi::laux::{luaL_checklstring, luaL_checkudata, luaL_setmetatable, luaL_testudata}; +use crate::ffi::lua::{ + lua_newuserdata, lua_pushboolean, lua_pushinteger, lua_pushlstring, lua_pushnil, + lua_pushnumber, lua_type, RawInteger, RawNumber, Type, +}; +use crate::util::core::SimpleDrop; +use crate::vm::function::{FromParam, IntoParam}; +use crate::vm::userdata::UserData; +use crate::vm::util::{lua_rust_error, LuaType, TypeName}; +use crate::vm::value::FromLua; +use crate::vm::Vm; +use std::borrow::Cow; +use std::error::Error; +use std::slice; + +impl<'a, T: FromParam<'a> + SimpleDrop> FromParam<'a> for Option { + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + let l = vm.as_ptr(); + let ty = lua_type(l, index); + if ty == Type::Nil || ty == Type::None { + None + } else { + Some(T::from_param(vm, index)) + } + } + + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + let l = vm.as_ptr(); + let ty = unsafe { lua_type(l, index) }; + if ty == Type::Nil || ty == Type::None { + None + } else { + Some(T::try_from_param(vm, index)) + } + } +} + +impl LuaType for &str {} + +impl<'a> FromParam<'a> for &'a str { + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + let mut len: usize = 0; + let str = luaL_checklstring(vm.as_ptr(), index, &mut len as _); + let slice = slice::from_raw_parts(str as *const u8, len); + match std::str::from_utf8(slice) { + Ok(v) => v, + Err(e) => { + lua_rust_error(vm.as_ptr(), e); + } + } + } + + #[inline(always)] + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } +} + +impl LuaType for &[u8] {} + +impl<'a> FromParam<'a> for &'a [u8] { + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + let mut len: usize = 0; + let str = luaL_checklstring(vm.as_ptr(), index, &mut len as _); + let slice = slice::from_raw_parts(str as *const u8, len); + slice + } + + #[inline(always)] + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } +} + +unsafe impl IntoParam for &str { + #[inline(always)] + fn into_param(self, vm: &Vm) -> u16 { + unsafe { + lua_pushlstring(vm.as_ptr(), self.as_ptr() as _, self.len()); + } + 1 + } +} + +unsafe impl IntoParam for &[u8] { + #[inline(always)] + fn into_param(self, vm: &Vm) -> u16 { + unsafe { + lua_pushlstring(vm.as_ptr(), self.as_ptr() as _, self.len()); + } + 1 + } +} + +unsafe impl IntoParam for String { + #[inline(always)] + fn into_param(self, vm: &Vm) -> u16 { + (&*self).into_param(vm) + } +} + +unsafe impl IntoParam for Vec { + fn into_param(self, vm: &Vm) -> u16 { + self.as_slice().into_param(vm) + } +} + +unsafe impl<'a, T: IntoParam + Clone> IntoParam for Cow<'a, T> +where + &'a T: IntoParam, +{ + fn into_param(self, vm: &Vm) -> u16 { + match self { + Cow::Borrowed(v) => v.into_param(vm), + Cow::Owned(v) => v.into_param(vm), + } + } +} + +macro_rules! impl_integer { + ($($t: ty),*) => { + $( + unsafe impl SimpleDrop for $t {} + + impl LuaType for $t { + fn lua_type() -> Vec { + vec![TypeName::Some(std::any::type_name::())] + } + } + + impl FromParam<'_> for $t { + #[inline(always)] + unsafe fn from_param(vm: &Vm, index: i32) -> Self { + lua_ext_fast_checkinteger(vm.as_ptr(), index) as _ + } + + #[inline(always)] + fn try_from_param(vm: &Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } + } + + unsafe impl IntoParam for $t { + #[inline(always)] + fn into_param(self, vm: &Vm) -> u16 { + unsafe { + lua_pushinteger(vm.as_ptr(), self as _); + 1 + } + } + } + )* + }; +} + +#[cfg(target_pointer_width = "64")] +impl_integer!(i64, u64); + +impl_integer!(i8, u8, i16, u16, i32, u32); + +macro_rules! impl_float { + ($($t: ty),*) => { + $( + unsafe impl SimpleDrop for $t {} + + impl LuaType for $t { + fn lua_type() -> Vec { + vec![TypeName::Some(std::any::type_name::())] + } + } + + impl FromParam<'_> for $t { + #[inline(always)] + unsafe fn from_param(vm: &Vm, index: i32) -> Self { + lua_ext_fast_checknumber(vm.as_ptr(), index) as _ + } + + #[inline(always)] + fn try_from_param(vm: &Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } + } + + unsafe impl IntoParam for $t { + #[inline(always)] + fn into_param(self, vm: &Vm) -> u16 { + unsafe { + lua_pushnumber(vm.as_ptr(), self as _); + 1 + } + } + } + )* + }; +} + +impl_float!(f32, f64); + +unsafe impl IntoParam for bool { + #[inline(always)] + fn into_param(self, vm: &Vm) -> u16 { + unsafe { lua_pushboolean(vm.as_ptr(), if self { 1 } else { 0 }) }; + 1 + } +} + +unsafe impl IntoParam for Result { + fn into_param(self, vm: &Vm) -> u16 { + match self { + Ok(v) => v.into_param(vm), + Err(e) => unsafe { + lua_rust_error(vm.as_ptr(), e); + }, + } + } +} + +unsafe impl IntoParam for Option { + fn into_param(self, vm: &Vm) -> u16 { + match self { + None => unsafe { + lua_pushnil(vm.as_ptr()); + 1 + }, + Some(v) => v.into_param(vm), + } + } +} + +unsafe impl IntoParam for () { + #[inline(always)] + fn into_param(self, _: &Vm) -> u16 { + 0 + } +} + +impl LuaType for &T { + fn lua_type() -> Vec { + vec![TypeName::Some(unsafe { + T::CLASS_NAME.to_str().unwrap_unchecked() + })] + } +} + +impl<'a, T: UserData> FromParam<'a> for &'a T { + #[inline(always)] + unsafe fn from_param(vm: &'a Vm, index: i32) -> &'a T { + let obj_ptr = luaL_checkudata(vm.as_ptr(), index, T::CLASS_NAME.as_ptr()) as *const T; + unsafe { &*obj_ptr } + } + + #[inline(always)] + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + let ptr = unsafe { luaL_testudata(vm.as_ptr(), index, T::CLASS_NAME.as_ptr()) } as *const T; + if ptr.is_null() { + None + } else { + Some(unsafe { &*ptr }) + } + } +} + +unsafe impl IntoParam for T { + fn into_param(self, vm: &Vm) -> u16 { + let userdata = unsafe { lua_newuserdata(vm.as_ptr(), size_of::()) } as *mut T; + unsafe { userdata.write(self) }; + unsafe { luaL_setmetatable(vm.as_ptr(), T::CLASS_NAME.as_ptr()) }; + 1 + } +} + +macro_rules! count_tts { + () => {0}; + ($_head:tt $($tail:tt)*) => {1 + count_tts!($($tail)*)}; +} + +macro_rules! impl_into_param_tuple { + ($($name: ident: $name2: tt),*) => { + unsafe impl<$($name: IntoParam),*> IntoParam for ($($name),*) { + fn into_param(self, vm: &Vm) -> u16 { + $( + self.$name2.into_param(vm); + )* + count_tts!($($name)*) + } + } + }; +} + +impl_into_param_tuple!(T: 0, T1: 1); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8); +impl_into_param_tuple!(T: 0, T1: 1, T2: 2, T3: 3, T4: 4, T5: 5, T6: 6, T7: 7, T8: 8, T9: 9); diff --git a/core/src/vm/function/interface.rs b/core/src/vm/function/interface.rs new file mode 100644 index 0000000..8b8b7dd --- /dev/null +++ b/core/src/vm/function/interface.rs @@ -0,0 +1,85 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::util::core::SimpleDrop; +use crate::vm::util::LuaType; +use crate::vm::Vm; + +/// This trait represents a function return value. +/// +/// # Safety +/// +/// When implementing this trait, ensure that the number returned by +/// [into_param](IntoParam::into_param) is EXACTLY equal to the number of values pushed onto the lua +/// stack. If more or fewer than advertised values exists on the stack after the call then the impl +/// is considered UB. +pub unsafe trait IntoParam: Sized { + /// Turns self into a function return parameter. + /// + /// This function returns the number of parameters pushed onto the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to push this value to. + /// + /// returns: u16 + fn into_param(self, vm: &Vm) -> u16; +} + +/// This trait represents a function parameter. +pub trait FromParam<'a>: Sized + SimpleDrop + LuaType { + /// Reads this value from the given lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to read from. + /// * `index`: index of the parameter to read. This index is guaranteed to be absolute. + /// + /// returns: Self + /// + /// # Safety + /// + /// Calling this function outside the body of a [CFunction](crate::ffi::lua::CFunction) is UB. + /// Calling this function in a non-POF segment of that CFunction is also UB. If the index is not + /// absolute, this function may cause UB. + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self; + + /// Attempts to read this value from the given lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to read from. + /// * `index`: index of the parameter to read. This index is guaranteed to be absolute. + /// + /// returns: Self + /// + /// # Errors + /// + /// Returns a [None] value if this value cannot be read from the lua stack. + fn try_from_param(vm: &'a Vm, index: i32) -> Option; +} diff --git a/core/src/vm/function/mod.rs b/core/src/vm/function/mod.rs new file mode 100644 index 0000000..0f7fbf0 --- /dev/null +++ b/core/src/vm/function/mod.rs @@ -0,0 +1,33 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod core; +mod interface; +pub mod types; + +pub use interface::*; diff --git a/core/src/vm/function/types.rs b/core/src/vm/function/types.rs new file mode 100644 index 0000000..ff5fddc --- /dev/null +++ b/core/src/vm/function/types.rs @@ -0,0 +1,50 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_pushcclosure, CFunction}; +use crate::vm::value::IntoLua; +use crate::vm::Vm; + +pub struct RFunction(CFunction); + +impl RFunction { + #[inline(always)] + pub fn wrap(inner: CFunction) -> Self { + Self(inner) + } +} + +unsafe impl IntoLua for RFunction { + fn into_lua(self, vm: &Vm) -> u16 { + let l = vm.as_ptr(); + unsafe { + lua_pushcclosure(l, self.0, 0); + } + 1 + } +} diff --git a/core/src/vm/mod.rs b/core/src/vm/mod.rs new file mode 100644 index 0000000..f254c9b --- /dev/null +++ b/core/src/vm/mod.rs @@ -0,0 +1,45 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod closure; +pub mod core; +pub mod error; +pub mod function; +pub mod registry; +pub mod table; +pub mod thread; +pub mod userdata; +pub mod util; +pub mod value; + +pub use core::Vm; + +#[cfg(feature = "root-vm")] +pub use core::RootVm; + +pub type Result = std::result::Result; diff --git a/core/src/vm/registry/core.rs b/core/src/vm/registry/core.rs new file mode 100644 index 0000000..8df37f9 --- /dev/null +++ b/core/src/vm/registry/core.rs @@ -0,0 +1,212 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::{luaL_ref, luaL_unref}; +use crate::ffi::lua::{lua_rawgeti, lua_rawseti, REGISTRYINDEX}; +use crate::vm::registry::{FromIndex, Set, Value}; +use crate::vm::value::util::ensure_value_top; +use crate::vm::Vm; +use std::ffi::c_int; +use std::marker::PhantomData; + +//TODO: Check if key can be a NonZeroI32. + +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub struct RawKey(c_int); + +impl RawKey { + /// Returns the raw key. + #[inline(always)] + pub fn as_int(&self) -> c_int { + self.0 + } + + /// Wraps a raw integer as a registry key. + #[inline(always)] + pub fn from_int(v: c_int) -> RawKey { + RawKey(v) + } + + /// Pushes the lua value associated to this registry key on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to attach the produced lua value to. + /// + /// returns: ::Value + /// + /// # Safety + /// + /// This is UB to call if the key is invalid or already freed. + #[inline(always)] + pub unsafe fn push(&self, vm: &Vm) { + lua_rawgeti(vm.as_ptr(), REGISTRYINDEX, self.0); + } + + /// Deletes this registry key from the specified [Vm]. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to unregister from. + /// + /// returns: () + /// + /// # Safety + /// + /// This is UB to call if the registry key is invalid or was already freed. + #[inline(always)] + pub unsafe fn delete(self, vm: &Vm) { + luaL_unref(vm.as_ptr(), REGISTRYINDEX, self.0); + } + + /// Sets the content of this key with the value on top of the stack. + /// + /// # Arguments + /// + /// * `vm`: the vm associated with this key. + /// + /// returns: () + /// + /// # Safety + /// + /// This is UB to call if the key has already been deleted. + #[inline(always)] + pub unsafe fn set(&self, vm: &Vm) { + lua_rawseti(vm.as_ptr(), REGISTRYINDEX, self.0); + } + + /// Creates a new [RawKey] from the top of the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] instance representing the lua stack. + /// + /// returns: RegistryKey + /// + /// # Safety + /// + /// This is UB to call if the stack is empty. + #[inline(always)] + pub unsafe fn from_top(vm: &Vm) -> RawKey { + let key = unsafe { luaL_ref(vm.as_ptr(), REGISTRYINDEX) }; + RawKey(key) + } +} + +impl FromIndex for RawKey { + unsafe fn from_index(vm: &Vm, index: i32) -> Self { + ensure_value_top(vm, index); + Self::from_top(vm) + } +} + +impl Set for RawKey { + unsafe fn set(&self, vm: &Vm, index: i32) { + ensure_value_top(vm, index); + self.set(vm); + } +} + +pub struct Key { + raw: RawKey, + useless: PhantomData<*const T>, +} + +impl Key { + /// Pushes the lua value associated to this registry key on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to attach the produced lua value to. + /// + /// returns: ::Value + #[inline(always)] + pub fn push<'a>(&self, vm: &'a Vm) -> T::Value<'a> { + unsafe { + self.raw.push(vm); + T::from_registry(vm, -1) + } + } + + /// Pushes the lua value associated to this registry key on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to attach the produced lua value to. + /// + /// returns: ::Value + #[inline(always)] + pub fn as_raw(&self) -> RawKey { + self.raw + } + + /// Deletes this registry key from the specified [Vm]. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to unregister from. + /// + /// returns: () + #[inline(always)] + pub fn delete(self, vm: &Vm) { + unsafe { self.raw.delete(vm) }; + } + + /// Creates a new [Key] from the top of the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] instance representing the lua stack. + /// + /// returns: RegistryKey + /// + /// # Safety + /// + /// The type T must match the type of the value at the top of the stack. Additionally, the value + /// at the top of the stack must not be referenced as it will be popped. + pub unsafe fn from_top(vm: &Vm) -> Key { + Key { + raw: RawKey::from_top(vm), + useless: PhantomData, + } + } + + #[inline(always)] + pub fn new(value: T::Value<'_>) -> Key { + Key { + raw: T::push_registry(value), + useless: PhantomData, + } + } + + #[inline(always)] + pub fn set(&self, value: T::Value<'_>) { + unsafe { T::set_registry(&self.raw, value) } + } +} diff --git a/core/src/vm/registry/interface.rs b/core/src/vm/registry/interface.rs new file mode 100644 index 0000000..29fe9f8 --- /dev/null +++ b/core/src/vm/registry/interface.rs @@ -0,0 +1,107 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::vm::Vm; + +/// This trait represents a generic key which can be constructed from an index on the lua stack. +pub trait FromIndex { + /// Constructs a new instance of this generic key from the given vm and index. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] instance to manipulate. + /// * `index`: the index of the value on the lua stack. + /// + /// returns: Self + /// + /// # Safety + /// + /// This function removes the value at index `index` and so assumes no more references exists + /// to it, failure to ensure this is UB. + unsafe fn from_index(vm: &Vm, index: i32) -> Self; +} + +/// This trait represents a generic key which can be set from an index on the lua stack. +pub trait Set { + /// Sets the value of this generic key from the given vm and index. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] instance to manipulate. + /// * `index`: the index of the value on the lua stack. + /// + /// returns: () + /// + /// # Safety + /// + /// This function removes the value at index `index` and so assumes no more references exists + /// to it, failure to ensure this is UB. The function also assumes this generic key still + /// exists in the registry table. Finally, this assumes this key does not conflict with a + /// different one. + unsafe fn set(&self, vm: &Vm, index: i32); +} + +pub trait Value: 'static { + type Value<'a>; + + /// Reads the upvalue at the given location on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to read from. + /// * `index`: the index of the value. This index is not guaranteed to be absolute. + /// + /// returns: Self::Value + /// + /// # Safety + /// + /// This function assumes the value at the top of the stack is of type `Self`. This function is + /// UB otherwise. + unsafe fn from_registry(vm: &Vm, index: i32) -> Self::Value<'_>; + + /// Intializes a new generic key from the given value. + /// + /// This function should call R::from_index with a matching index and [Vm] instance. + fn push_registry(value: Self::Value<'_>) -> R; + + /// Assign this value to the given generic registry key. + /// + /// This function should call key.set with a matching index and [Vm] instance. + /// + /// # Arguments + /// + /// * `key`: the key to update. + /// * `value`: the new value. + /// + /// returns: () + /// + /// # Safety + /// + /// This function assumes the generic key still exists in the registry table. + unsafe fn set_registry(key: &impl Set, value: Self::Value<'_>); +} diff --git a/core/src/vm/registry/mod.rs b/core/src/vm/registry/mod.rs new file mode 100644 index 0000000..067427b --- /dev/null +++ b/core/src/vm/registry/mod.rs @@ -0,0 +1,34 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod core; +mod interface; +pub mod named; +pub mod types; + +pub use interface::*; diff --git a/core/src/vm/registry/named.rs b/core/src/vm/registry/named.rs new file mode 100644 index 0000000..e449ac0 --- /dev/null +++ b/core/src/vm/registry/named.rs @@ -0,0 +1,242 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{ + lua_createtable, lua_insert, lua_pushboolean, lua_pushlightuserdata, lua_pushnil, lua_rawget, + lua_rawset, lua_settop, lua_type, State, Type, REGISTRYINDEX, +}; +use crate::vm::registry::{Set, Value}; +use crate::vm::value::util::ensure_value_top; +use crate::vm::Vm; +use std::cell::Cell; +use std::ffi::c_void; +use std::marker::PhantomData; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct RawKey(*const c_void); + +unsafe impl Send for RawKey {} +unsafe impl Sync for RawKey {} + +impl RawKey { + /// Pushes the value associated with this key on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] instance to manipulate. + /// + /// returns: () + /// + /// # Safety + /// + /// This is UB to call if the key was not registered in the given [Vm] using + /// [register](RawKey::register). + pub unsafe fn push(&self, vm: &Vm) { + let l = vm.as_ptr(); + unsafe { + lua_pushlightuserdata(l, self.0 as _); + lua_rawget(l, REGISTRYINDEX); + } + } + + /// Attempts to register this key with the given [Vm] instance. This function ensures the key + /// does not collide. + /// + /// # Panic + /// + /// This function panics if this key is already registered in the given [Vm]. + #[inline(always)] + pub fn register(&self, vm: &Vm) { + unsafe { check_key_already_used(vm, self.0) }; + } + + pub const fn new(name: &str) -> Self { + // This is a re-write of https://github.com/BPXFormat/bpx-rs/blob/develop/src/hash.rs + // in const context. + let mut val: u64 = 5381; + let bytes = name.as_bytes(); + // Unreadable algorithm: see the hash loop in bpx-rs for the readable variant. + let mut i = 0; + while i != bytes.len() { + let temp1 = val.wrapping_shl(5); + let temp2 = temp1.wrapping_add(val); + val = temp2.wrapping_add(bytes[i] as u64); + i += 1; + } + // And now a hack to turn u64 into ptr (btw, do NOT dereference it). + Self(val as usize as *const c_void) + } +} + +unsafe fn rawsetp(l: State, idx: i32, key: *const c_void) { + lua_pushlightuserdata(l, key as _); + lua_insert(l, -2); // Move key after value; + lua_rawset(l, idx); +} + +impl Set for RawKey { + unsafe fn set(&self, vm: &Vm, index: i32) { + let l = vm.as_ptr(); + ensure_value_top(vm, index); + rawsetp(l, REGISTRYINDEX, self.0); + } +} + +struct InitKey(*const c_void); + +const USED_KEYS: RawKey = RawKey::new("__used_keys__"); + +unsafe fn check_key_already_used(vm: &Vm, key: *const c_void) { + if key == USED_KEYS.0 { + panic!("Attempt to use reserved named key __used_keys__"); + } + let l = vm.as_ptr(); + USED_KEYS.push(vm); + if lua_type(l, -1) != Type::Table { + lua_settop(l, -2); // Clear nil from the top of the stack. + lua_createtable(l, 0, 0); + USED_KEYS.set(vm, -1); // Pop the table and set it in the registry. + USED_KEYS.push(vm); // Re-push the table so that following code can use it. + } + lua_pushlightuserdata(l, key as _); + lua_rawget(l, -2); // Table is now at index -2 on the stack. + let ty = lua_type(l, -1); + lua_settop(l, -2); // Remove value from stack. + if ty == Type::Boolean { + // Key is already taken, this is bad. + panic!("Attempt to register an already used named key"); + } + lua_pushlightuserdata(l, key as _); + lua_pushboolean(l, 1); + lua_rawset(l, -3); // Table is now at -3 on the stack because we've just pushed a key and a + // value. + lua_settop(l, -2); // Clear the used keys table from the stack. +} + +impl Set for InitKey { + unsafe fn set(&self, vm: &Vm, index: i32) { + check_key_already_used(vm, self.0); + let l = vm.as_ptr(); + ensure_value_top(vm, index); + rawsetp(l, REGISTRYINDEX, self.0); + } +} + +#[derive(Clone)] +pub struct Key { + raw: RawKey, + registered: Cell, + useless: PhantomData<*const T>, +} + +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.raw.eq(&other.raw) + } +} + +impl Eq for Key {} + +impl Key { + #[inline(always)] + fn ensure_registered(&self, vm: &Vm) { + if !self.registered.get() { + unsafe { check_key_already_used(vm, self.raw.0) }; + self.registered.set(true); + } + } + + /// Pushes the lua value associated to this registry key on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to attach the produced lua value to. + /// + /// returns: ::Value + pub fn push<'a>(&self, vm: &'a Vm) -> Option> { + self.ensure_registered(vm); + unsafe { + self.raw.push(vm); + if lua_type(vm.as_ptr(), -1) == Type::Nil { + lua_settop(vm.as_ptr(), -2); + return None; + } + Some(T::from_registry(vm, -1)) + } + } + + /// Pushes the lua value associated to this registry key on the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to attach the produced lua value to. + /// + /// returns: ::Value + #[inline(always)] + pub fn as_raw(&self) -> RawKey { + self.raw + } + + /// Resets the value pointed to by this registry key from the specified [Vm]. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to unregister from. + /// + /// returns: () + #[inline(always)] + pub fn reset(&self, vm: &Vm) { + unsafe { + lua_pushnil(vm.as_ptr()); + self.raw.set(vm, -1); + } + } + + /// Sets the value for this key. + /// + /// # Arguments + /// + /// * `value`: the value to replace with. + /// + /// returns: () + pub fn set(&self, value: T::Value<'_>) { + if self.registered.get() { + unsafe { T::set_registry(&InitKey(self.raw.0), value) }; + } else { + unsafe { T::set_registry(&self.raw, value) }; + } + } + + pub const fn new(name: &str) -> Self { + Key { + raw: RawKey::new(name), + registered: Cell::new(false), + useless: PhantomData, + } + } +} diff --git a/src/lib.rs b/core/src/vm/registry/types.rs similarity index 95% rename from src/lib.rs rename to core/src/vm/registry/types.rs index 250e249..42e57e0 100644 --- a/src/lib.rs +++ b/core/src/vm/registry/types.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2021, BlockProject 3D +// Copyright (c) 2025, BlockProject 3D // // All rights reserved. // @@ -26,3 +26,5 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +pub struct Table; +pub struct Function; diff --git a/core/src/vm/table/core.rs b/core/src/vm/table/core.rs new file mode 100644 index 0000000..e5bc701 --- /dev/null +++ b/core/src/vm/table/core.rs @@ -0,0 +1,192 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::ext::{lua_ext_tab_len, MSize}; +use crate::ffi::lua::{ + lua_createtable, lua_gettable, lua_gettop, lua_objlen, lua_pushvalue, lua_rawseti, + lua_setmetatable, lua_settable, lua_topointer, +}; +use crate::vm::table::iter::Iter; +use crate::vm::table::traits::{GetTable, SetTable}; +use crate::vm::value::util::ensure_single_into_lua; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; +use std::fmt::{Debug, Display}; + +pub struct Table<'a> { + pub(super) vm: &'a Vm, + index: i32, +} + +impl Clone for Table<'_> { + fn clone(&self) -> Self { + unsafe { lua_pushvalue(self.vm.as_ptr(), self.index) }; + Table { + vm: self.vm, + index: self.vm.top(), + } + } +} + +impl PartialEq for Table<'_> { + fn eq(&self, other: &Self) -> bool { + let a = unsafe { lua_topointer(self.vm.as_ptr(), self.index) }; + let b = unsafe { lua_topointer(other.vm.as_ptr(), other.index) }; + a == b + } +} + +impl Eq for Table<'_> {} + +impl Display for Table<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "table@{:X}", + unsafe { lua_topointer(self.vm.as_ptr(), self.index) } as usize + ) + } +} + +impl Debug for Table<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Table({:?})", self.index) + } +} + +impl<'a> Table<'a> { + /// Creates a table from a raw Vm and index. + /// + /// # Arguments + /// + /// * `vm`: the vm to link to. + /// * `index`: the index on the lua stack. + /// + /// returns: Table + /// + /// # Safety + /// + /// Must ensure that index points to a table and is absolute. If index is not absolute then + /// using the produced table is UB. If the index points to any other type then using the produced + /// table is also UB. + #[inline(always)] + pub unsafe fn from_raw(vm: &'a Vm, index: i32) -> Self { + Self { vm, index } + } + + pub fn new(vm: &'a Vm) -> Self { + unsafe { lua_createtable(vm.as_ptr(), 0, 0) }; + let index = unsafe { lua_gettop(vm.as_ptr()) }; + Self { vm, index } + } + + pub fn with_capacity(vm: &'a Vm, array_capacity: usize, non_array_capcity: usize) -> Self { + unsafe { lua_createtable(vm.as_ptr(), array_capacity as _, non_array_capcity as _) }; + let index = unsafe { lua_gettop(vm.as_ptr()) }; + Self { vm, index } + } + + pub fn len(&self) -> usize { + let mut size: MSize = 0; + let ret = unsafe { lua_ext_tab_len(self.vm.as_ptr(), self.index, &mut size) }; + if ret == 0 { + return size as _; + } + Iter::from_raw(self.vm, self.index).count() as _ + } + + pub fn set_metatable(&mut self, other: Table) { + other.into_lua(self.vm); + unsafe { lua_setmetatable(self.vm.as_ptr(), self.index) }; + } + + /// Returns the absolute index of this table on the Lua stack. + #[inline(always)] + pub fn index(&self) -> i32 { + self.index + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Creates a new iterator for this table. + /// + /// This function borrows mutably to avoid messing up the Lua stack while iterating. + pub fn iter(&mut self) -> Iter { + Iter::from_raw(self.vm, self.index) + } + + pub fn get<'b, T: FromLua<'b>>(&'b self, key: impl GetTable) -> crate::vm::Result { + if T::num_values() != 1 { + return Err(crate::vm::error::Error::MultiValue); + } + unsafe { + key.get_table(self.vm.as_ptr(), self.index)?; + T::from_lua(self.vm, -1) + } + } + + pub fn set(&mut self, key: impl SetTable, value: impl IntoLua) -> crate::vm::Result<()> { + unsafe { + ensure_single_into_lua(self.vm, value)?; + key.set_table(self.vm.as_ptr(), self.index)?; + } + Ok(()) + } + + pub fn get_any<'b, T: FromLua<'b>>(&'b self, key: impl IntoLua) -> crate::vm::Result { + if T::num_values() != 1 { + return Err(crate::vm::error::Error::MultiValue); + } + unsafe { + ensure_single_into_lua(self.vm, key)?; + lua_gettable(self.vm.as_ptr(), self.index); + T::from_lua(self.vm, -1) + } + } + + pub fn set_any(&mut self, key: impl IntoLua, value: impl IntoLua) -> crate::vm::Result<()> { + unsafe { + ensure_single_into_lua(self.vm, key)?; + ensure_single_into_lua(self.vm, value)?; + lua_settable(self.vm.as_ptr(), self.index); + } + Ok(()) + } + + pub fn push(&mut self, value: impl IntoLua) -> crate::vm::Result<()> { + unsafe { + let len = lua_objlen(self.vm.as_ptr(), self.index); + ensure_single_into_lua(self.vm, value)?; + lua_rawseti(self.vm.as_ptr(), self.index, len as i32 + 1); + } + Ok(()) + } +} diff --git a/core/src/vm/table/interface.rs b/core/src/vm/table/interface.rs new file mode 100644 index 0000000..3e9986a --- /dev/null +++ b/core/src/vm/table/interface.rs @@ -0,0 +1,138 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::luaL_checktype; +use crate::ffi::lua::{ + lua_getfield, lua_gettop, lua_pushvalue, lua_rawgeti, lua_rawseti, lua_setfield, lua_type, + State, Type, +}; +use crate::util::core::{AnyStr, SimpleDrop}; +use crate::vm::function::{FromParam, IntoParam}; +use crate::vm::registry::{FromIndex, Set}; +use crate::vm::table::traits::{GetTable, SetTable}; +use crate::vm::table::Table; +use crate::vm::util::LuaType; +use crate::vm::value::util::ensure_type_equals; +use crate::vm::value::FromLua; +use crate::vm::Vm; + +unsafe impl SimpleDrop for Table<'_> {} + +impl<'a> FromParam<'a> for Table<'a> { + #[inline(always)] + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + luaL_checktype(vm.as_ptr(), index, Type::Table); + Table::from_raw(vm, index) + } + + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + if unsafe { lua_type(vm.as_ptr(), index) } != Type::Table { + return None; + } + Some(unsafe { Table::from_raw(vm, index) }) + } +} + +impl<'a> FromLua<'a> for Table<'a> { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + Table::from_raw(vm, vm.get_absolute_index(index)) + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + ensure_type_equals(vm, index, Type::Table)?; + Ok(unsafe { Table::from_raw(vm, vm.get_absolute_index(index)) }) + } +} + +unsafe impl IntoParam for Table<'_> { + fn into_param(self, vm: &Vm) -> u16 { + let top = unsafe { lua_gettop(vm.as_ptr()) }; + if top != self.index() { + unsafe { lua_pushvalue(vm.as_ptr(), self.index()) }; + } + 1 + } +} + +impl LuaType for Table<'_> {} + +impl crate::vm::registry::Value for crate::vm::registry::types::Table { + type Value<'a> = Table<'a>; + + #[inline(always)] + unsafe fn from_registry(vm: &Vm, index: i32) -> Self::Value<'_> { + unsafe { Table::from_lua_unchecked(vm, index) } + } + + #[inline(always)] + fn push_registry(value: Self::Value<'_>) -> R { + unsafe { R::from_index(value.vm, value.index()) } + } + + #[inline(always)] + unsafe fn set_registry(key: &impl Set, value: Self::Value<'_>) { + key.set(value.vm, value.index()) + } +} + +impl GetTable for T { + unsafe fn get_table(self, l: State, index: i32) -> crate::vm::Result<()> { + lua_getfield(l, index, self.to_str()?.as_ptr()); + Ok(()) + } +} + +macro_rules! impl_get_set_table { + ($($t: ty),*) => { + $( + impl GetTable for $t { + unsafe fn get_table(self, l: State, index: i32) -> crate::vm::Result<()> { + lua_rawgeti(l, index, self as _); + Ok(()) + } + } + + impl SetTable for $t { + unsafe fn set_table(self, l: State, index: i32) -> crate::vm::Result<()> { + lua_rawseti(l, index, self as _); + Ok(()) + } + } + )* + }; +} + +impl_get_set_table!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize); + +impl SetTable for T { + unsafe fn set_table(self, l: State, index: i32) -> crate::vm::Result<()> { + lua_setfield(l, index, self.to_str()?.as_ptr()); + Ok(()) + } +} diff --git a/core/src/vm/table/iter.rs b/core/src/vm/table/iter.rs new file mode 100644 index 0000000..8a2c840 --- /dev/null +++ b/core/src/vm/table/iter.rs @@ -0,0 +1,99 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_next, lua_pushnil, lua_settop}; +use crate::vm::value::any::AnyValue; +use crate::vm::value::FromLua; +use crate::vm::Vm; + +pub struct Iter<'a> { + vm: &'a Vm, + index: i32, + has_ended: bool, + has_started: bool, + last_top: i32, +} + +impl<'a> Iter<'a> { + pub(super) fn from_raw(vm: &'a Vm, index: i32) -> Self { + // Push a nil value on the stack to allow the iterator to work. + unsafe { lua_pushnil(vm.as_ptr()) }; + Self { + vm, + index, + has_ended: false, + has_started: false, + last_top: vm.top(), + } + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = crate::vm::Result<(AnyValue<'a>, AnyValue<'a>)>; + + fn next(&mut self) -> Option { + if self.has_started { + // This ensures the iterator remains safe. + if self.vm.top() != self.last_top { + panic!( + "Attempt to iterate on moved values (expected Vm top: {}, got: {})", + self.last_top, + self.vm.top() + ); + } + // Pop the last value on the stack which corresponds to the last value from lua_next. + // Only if the iterator was started. + unsafe { lua_settop(self.vm.as_ptr(), -2) }; + } + let ret = unsafe { lua_next(self.vm.as_ptr(), self.index) }; + self.last_top = self.vm.top(); + self.has_started = true; + if ret != 0 { + let value = AnyValue::from_lua(self.vm, -2); + let key = AnyValue::from_lua(self.vm, -1); + Some(match (value, key) { + (Ok(key), Ok(value)) => Ok((key, value)), + (Ok(_), Err(e)) => Err(e), + (Err(e), Ok(_)) => Err(e), + (Err(_), Err(e)) => Err(e), + }) + } else { + self.has_ended = true; + None + } + } +} + +impl Drop for Iter<'_> { + fn drop(&mut self) { + if !self.has_ended { + // If the iterator did not reach the end, clear key-value pair from the lua stack. + unsafe { lua_settop(self.vm.as_ptr(), -3) }; + } + } +} diff --git a/core/src/vm/table/mod.rs b/core/src/vm/table/mod.rs new file mode 100644 index 0000000..07f84ba --- /dev/null +++ b/core/src/vm/table/mod.rs @@ -0,0 +1,36 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//TODO: Support collect function + +mod core; +mod interface; +mod iter; +pub mod traits; + +pub use core::Table; diff --git a/core/src/vm/table/traits.rs b/core/src/vm/table/traits.rs new file mode 100644 index 0000000..0602d33 --- /dev/null +++ b/core/src/vm/table/traits.rs @@ -0,0 +1,77 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::State; + +pub trait GetTable { + /// Attempts to push the value identified by key `self` contained in table at index `index` onto + /// the lua stack. + /// + /// # Arguments + /// + /// * `l`: the lua state. + /// * `index`: the index of the table to read from. + /// + /// returns: () + /// + /// # Errors + /// + /// This returns an [Error](crate::vm::error::Error) if the key identified by `self` is not + /// valid for the table at index `index` on the lua stack. + /// + /// # Safety + /// + /// The lua state pointer `l` must not be NULL and must point to a valid [Vm], and the index + /// `index` must point to a valid [Table] on the lua stack. If any of these invariants are not + /// respected, this function is UB. + unsafe fn get_table(self, l: State, index: i32) -> crate::vm::Result<()>; +} + +pub trait SetTable { + /// Attempts to write the value on top of the lua stack into the key identified by `self` in + /// the table at index `index`. + /// + /// # Arguments + /// + /// * `l`: the lua state. + /// * `index`: the index of the table to read from. + /// + /// returns: () + /// + /// # Errors + /// + /// This returns an [Error](crate::vm::error::Error) if the key identified by `self` is not + /// valid for the table at index `index` on the lua stack. + /// + /// # Safety + /// + /// The lua state pointer `l` must not be NULL and must point to a valid [Vm], and the index + /// `index` must point to a valid [Table] on the lua stack. If any of these invariants are not + /// respected, this function is UB. + unsafe fn set_table(self, l: State, index: i32) -> crate::vm::Result<()>; +} diff --git a/core/src/vm/thread.rs b/core/src/vm/thread.rs new file mode 100644 index 0000000..e64a779 --- /dev/null +++ b/core/src/vm/thread.rs @@ -0,0 +1,152 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::luaL_checktype; +use crate::ffi::lua::{lua_remove, lua_resume, lua_status, lua_tothread, ThreadStatus, Type}; +use crate::util::core::SimpleDrop; +use crate::vm::core::LoadString; +use crate::vm::error::{Error, RuntimeError}; +use crate::vm::function::FromParam; +use crate::vm::util::LuaType; +use crate::vm::value::util::ensure_type_equals; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; +use std::fmt::{Debug, Display}; +use std::marker::PhantomData; + +pub enum State { + Yielded, + Finished, +} + +pub struct Thread<'a> { + vm: Vm, + useless: PhantomData<&'a ()>, +} + +impl Clone for Thread<'_> { + fn clone(&self) -> Self { + Self { + vm: unsafe { Vm::from_raw(self.vm.as_ptr()) }, + useless: PhantomData, + } + } +} + +impl PartialEq for Thread<'_> { + fn eq(&self, other: &Self) -> bool { + self.vm.as_ptr() == other.vm.as_ptr() + } +} + +impl Eq for Thread<'_> {} + +impl Display for Thread<'_> { + #[allow(clippy::missing_transmute_annotations)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "thread@{:X}", unsafe { + std::mem::transmute::<_, usize>(self.vm.as_ptr()) + }) + } +} + +impl Debug for Thread<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Thread") + } +} + +impl Thread<'_> { + #[inline(always)] + pub fn run_code<'b, R: FromLua<'b>>(&'b self, code: impl LoadString) -> crate::vm::Result { + self.vm.run_code(code) + } + + #[inline(always)] + pub fn status(&self) -> ThreadStatus { + unsafe { lua_status(self.vm.as_ptr()) } + } + + pub fn resume(&self, args: impl IntoLua) -> crate::vm::Result { + let num = args.into_lua(&self.vm); + let res = unsafe { lua_resume(self.vm.as_ptr(), num as _) }; + match res { + ThreadStatus::Ok => Ok(State::Finished), + ThreadStatus::Yield => Ok(State::Yielded), + ThreadStatus::ErrRun => { + // We've got a runtime error when executing the function so read the full stack + // trace produced by luaL_traceback and remove it from the stack. + let error_message: &str = FromLua::from_lua(&self.vm, -1)?; + unsafe { lua_remove(self.vm.as_ptr(), -1) }; + Err(Error::Runtime(RuntimeError::new( + String::from(error_message) + "\n", + ))) + } + ThreadStatus::ErrMem => Err(Error::Memory), + ThreadStatus::ErrErr => Err(Error::Error), + _ => std::unreachable!(), + } + } +} + +impl<'a> FromLua<'a> for Thread<'a> { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + Thread { + vm: Vm::from_raw(lua_tothread(vm.as_ptr(), index)), + useless: PhantomData, + } + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + ensure_type_equals(vm, index, Type::Thread)?; + Ok(Thread { + vm: unsafe { Vm::from_raw(lua_tothread(vm.as_ptr(), index)) }, + useless: PhantomData, + }) + } +} + +unsafe impl SimpleDrop for Thread<'_> {} + +impl LuaType for Thread<'_> {} + +impl<'a> FromParam<'a> for Thread<'a> { + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + luaL_checktype(vm.as_ptr(), index, Type::Thread); + Thread { + vm: unsafe { Vm::from_raw(lua_tothread(vm.as_ptr(), index)) }, + useless: PhantomData, + } + } + + #[inline(always)] + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } +} diff --git a/core/src/vm/userdata/any.rs b/core/src/vm/userdata/any.rs new file mode 100644 index 0000000..e8a12a8 --- /dev/null +++ b/core/src/vm/userdata/any.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::luaL_testudata; +use crate::ffi::lua::{lua_pushvalue, lua_topointer, lua_touserdata, lua_type, Type}; +use crate::vm::error::{Error, TypeError}; +use crate::vm::userdata::{UserData, UserDataImmutable}; +use crate::vm::value::FromLua; +use crate::vm::Vm; +use std::fmt::{Debug, Display}; + +pub struct AnyUserData<'a> { + vm: &'a Vm, + index: i32, +} + +impl Clone for AnyUserData<'_> { + fn clone(&self) -> Self { + unsafe { lua_pushvalue(self.vm.as_ptr(), self.index) }; + AnyUserData { + vm: self.vm, + index: self.vm.top(), + } + } +} + +impl PartialEq for AnyUserData<'_> { + fn eq(&self, other: &Self) -> bool { + let a = unsafe { lua_topointer(self.vm.as_ptr(), self.index) }; + let b = unsafe { lua_topointer(other.vm.as_ptr(), other.index) }; + a == b + } +} + +impl Eq for AnyUserData<'_> {} + +impl Display for AnyUserData<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "userdata@{:X}", + unsafe { lua_touserdata(self.vm.as_ptr(), self.index) } as usize + ) + } +} + +impl Debug for AnyUserData<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "UserData({:?})", self.index) + } +} + +impl<'a> AnyUserData<'a> { + /// Creates an AnyUserData from a raw Vm and index. + /// + /// # Arguments + /// + /// * `vm`: the vm to link to. + /// * `index`: the index on the lua stack. + /// + /// returns: Table + /// + /// # Safety + /// + /// Must ensure that index points to a UserData and is absolute. If index is not absolute then + /// using the produced object is UB. If the index points to any other type then using the produced + /// object is also UB. + #[inline(always)] + pub unsafe fn from_raw(vm: &'a Vm, index: i32) -> Self { + Self { vm, index } + } + + /// Returns a reference to this UserData value cast to `T`. + #[inline(always)] + pub fn get(&self) -> crate::vm::Result<&T> { + crate::vm::value::FromLua::from_lua(self.vm, self.index) + } + + /// Returns a mutable reference to a UserData value. + /// + /// # Safety + /// + /// The caller is responsible for guaranteeing that only a single reference to this object is + /// created. That is no other references to this underlying userdata value must exist in Rust + /// code otherwise using this function is UB. + pub unsafe fn get_mut(&mut self) -> crate::vm::Result<&mut T> { + let this_ptr = + unsafe { luaL_testudata(self.vm.as_ptr(), self.index, T::CLASS_NAME.as_ptr()) } + as *mut T; + if this_ptr.is_null() { + return Err(Error::Type(TypeError { + expected: Type::Userdata, + actual: unsafe { lua_type(self.vm.as_ptr(), self.index) }, + })); + } + Ok(unsafe { &mut *this_ptr }) + } +} + +impl<'a> FromLua<'a> for AnyUserData<'a> { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + AnyUserData::from_raw(vm, vm.get_absolute_index(index)) + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let ty = unsafe { lua_type(vm.as_ptr(), index) }; + if ty == Type::Userdata { + Ok(unsafe { AnyUserData::from_raw(vm, vm.get_absolute_index(index)) }) + } else { + Err(Error::Type(TypeError { + expected: Type::Userdata, + actual: ty, + })) + } + } +} diff --git a/core/src/vm/userdata/case.rs b/core/src/vm/userdata/case.rs new file mode 100644 index 0000000..ee4caf5 --- /dev/null +++ b/core/src/vm/userdata/case.rs @@ -0,0 +1,91 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::vm::userdata::NameConvert; +use bp3d_util::string::BufTools; +use itertools::Itertools; +use std::borrow::Cow; +use std::ffi::{CStr, CString}; + +fn to_string_lossy(bytes: Cow<[u8]>) -> Cow { + match bytes { + Cow::Borrowed(v) => String::from_utf8_lossy(v), + Cow::Owned(v) => String::from(&*String::from_utf8_lossy(&v)).into(), + } +} + +pub struct Snake; + +impl NameConvert for Snake { + fn name_convert(&self, name: &'static CStr) -> Cow<'static, CStr> { + Cow::Borrowed(name) + } +} + +pub struct Camel; + +impl NameConvert for Camel { + fn name_convert(&self, name: &'static CStr) -> Cow<'static, CStr> { + let s = match name.to_str() { + Ok(v) => v, + // Return the same unconverted string if we failed. + Err(_) => return Cow::Borrowed(name), + }; + let s: String = s + .split("_") + .enumerate() + .map(|(i, v)| { + if i != 0 { + v.as_bytes().capitalise_ascii() + } else { + v.as_bytes().into() + } + }) + .map(to_string_lossy) + .join(""); + CString::new(s).unwrap().into() + } +} + +pub struct Pascal; + +impl NameConvert for Pascal { + fn name_convert(&self, name: &'static CStr) -> Cow<'static, CStr> { + let s = match name.to_str() { + Ok(v) => v, + // Return the same unconverted string if we failed. + Err(_) => return Cow::Borrowed(name), + }; + let s: String = s + .split("_") + .map(|v| v.as_bytes().capitalise_ascii()) + .map(to_string_lossy) + .join(""); + CString::new(s).unwrap().into() + } +} diff --git a/core/src/vm/userdata/core.rs b/core/src/vm/userdata/core.rs new file mode 100644 index 0000000..819c072 --- /dev/null +++ b/core/src/vm/userdata/core.rs @@ -0,0 +1,268 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::{luaL_checkudata, luaL_newmetatable}; +use crate::ffi::lua::{ + lua_pushcclosure, lua_pushnil, lua_pushvalue, lua_setfield, lua_setmetatable, lua_settop, + CFunction, State, +}; +use crate::vm::userdata::{AddGcMethod, Error, LuaDrop, NameConvert, UserData}; +use crate::vm::util::{LuaType, TypeName}; +use crate::vm::value::IntoLua; +use crate::vm::Vm; +use bp3d_debug::{debug, warning}; +use std::cell::OnceCell; +use std::ffi::CStr; +use std::marker::PhantomData; + +#[derive(Copy, Clone)] +pub struct Function { + pub name: &'static CStr, + pub func: CFunction, +} + +pub struct Builder { + is_mutable: bool, + args: Vec, + f: Function, +} + +impl Builder { + pub fn new(name: &'static CStr, func: CFunction) -> Builder { + Builder { + is_mutable: false, + args: Vec::new(), + f: Function { name, func }, + } + } + + pub fn mutable(&mut self) -> &mut Self { + self.is_mutable = true; + self + } + + pub fn arg(&mut self) -> &mut Self { + for ty in T::lua_type() { + self.args.push(ty); + } + self + } + + /// Checks and builds this userdata function + /// + /// # Safety + /// + /// All function arguments must be added through the arg function, if not calling this function + /// is considered UB. + pub unsafe fn build(&self) -> Result { + if self.args.is_empty() { + return Err(Error::ArgsEmpty); + } + if self.f.name == c"__gc" { + return Err(Error::Gc); + } + if self.f.name == c"__index" { + return Err(Error::Index); + } + if self.f.name == c"__metatable" { + return Err(Error::Metatable); + } + if self.is_mutable { + let initial = &self.args[0]; + for v in self.args.iter().skip(1) { + if initial == v { + return Err(Error::MutViolation(self.f.name)); + } + } + } + Ok(self.f) + } +} + +pub struct Registry<'a, T: UserData, C: NameConvert> { + vm: &'a Vm, + useless: PhantomData, + has_gc: OnceCell<()>, + case: C, +} + +impl<'a, T: UserData, C: NameConvert> Registry<'a, T, C> { + /// Creates a new [Registry] from the given Vm. + /// + /// # Arguments + /// + /// * `vm`: the vm in which to register the userdata metatable. + /// * `case`: the case converter to apply to each name to be registered. + /// + /// returns: Result, Error> + /// + /// # Safety + /// + /// Running operations on the vm after calling this method is UB unless this [Registry] object + /// is dropped. + pub unsafe fn new(vm: &'a Vm, case: C) -> Result { + if align_of::() > 8 { + return Err(Error::Alignment(align_of::())); + } + let res = unsafe { luaL_newmetatable(vm.as_ptr(), T::CLASS_NAME.as_ptr()) }; + if res != 1 { + unsafe { lua_settop(vm.as_ptr(), -2) }; + return Err(Error::AlreadyRegistered(T::CLASS_NAME)); + } + let reg = Registry { + vm, + useless: PhantomData, + has_gc: OnceCell::new(), + case, + }; + reg.add_field(c"__metatable", T::CLASS_NAME.to_str().unwrap_unchecked()) + .unwrap_unchecked(); + Ok(reg) + } + + pub fn add_field(&self, name: &'static CStr, value: impl IntoLua) -> Result<(), Error> { + let num = value.into_lua(self.vm); + if num > 1 { + unsafe { lua_settop(self.vm.as_ptr(), -(num as i32) - 1) }; + return Err(Error::MultiValueField); + } + unsafe { + lua_setfield(self.vm.as_ptr(), -2, self.case.name_convert(name).as_ptr()); + } + Ok(()) + } + + pub fn add_method(&self, f: Function) { + unsafe { + lua_pushcclosure(self.vm.as_ptr(), f.func, 0); + if &f.name.to_bytes()[..2] == b"__" { + lua_setfield(self.vm.as_ptr(), -2, f.name.as_ptr()); + } else { + lua_setfield( + self.vm.as_ptr(), + -2, + self.case.name_convert(f.name).as_ptr(), + ); + } + } + } + + pub fn add_gc_method(&self) { + if std::mem::needs_drop::() { + extern "C-unwind" fn run_drop(l: State) -> i32 { + unsafe { + let udata = luaL_checkudata(l, 1, T::CLASS_NAME.as_ptr()) as *mut T; + lua_pushnil(l); + lua_setmetatable(l, 1); + std::ptr::drop_in_place(udata); + } + 0 + } + self.add_method(Function { + name: c"__gc", + func: run_drop::, + }); + debug!({UD=?T::CLASS_NAME}, "Type registered with simple Drop"); + } + self.has_gc.set(()).unwrap(); + } +} + +impl Registry<'_, T, C> { + pub fn add_gc_method_with_lua_drop(&self) { + extern "C-unwind" fn run_lua_drop(l: State) -> i32 { + unsafe { + let udata = luaL_checkudata(l, 1, T::CLASS_NAME.as_ptr()) as *mut T; + lua_pushnil(l); + lua_setmetatable(l, 1); + (*udata).lua_drop(&Vm::from_raw(l)); + } + 0 + } + extern "C-unwind" fn run_lua_drop_full(l: State) -> i32 { + unsafe { + let udata = luaL_checkudata(l, 1, T::CLASS_NAME.as_ptr()) as *mut T; + lua_pushnil(l); + lua_setmetatable(l, 1); + (*udata).lua_drop(&Vm::from_raw(l)); + std::ptr::drop_in_place(udata); + } + 0 + } + if std::mem::needs_drop::() { + self.add_method(Function { + name: c"__gc", + func: run_lua_drop_full::, + }); + debug!({UD=?T::CLASS_NAME}, "Type registered with Drop and LuaDrop"); + } else { + self.add_method(Function { + name: c"__gc", + func: run_lua_drop::, + }); + debug!({UD=?T::CLASS_NAME}, "Type registered with LuaDrop"); + } + self.has_gc.set(()).unwrap(); + } +} + +pub struct AddGcMethodAuto(PhantomData); + +impl Default for AddGcMethodAuto { + fn default() -> Self { + AddGcMethodAuto(PhantomData) + } +} + +impl AddGcMethod for AddGcMethodAuto { + fn add_gc_method(&self, reg: &Registry) { + reg.add_gc_method_with_lua_drop(); + } +} + +impl AddGcMethod for &AddGcMethodAuto { + fn add_gc_method(&self, reg: &Registry) { + reg.add_gc_method(); + } +} + +impl Drop for Registry<'_, T, C> { + fn drop(&mut self) { + if std::mem::needs_drop::() && self.has_gc.get().is_none() { + warning!("No __gc method registered on a drop userdata type!"); + // No __gc method found in object that needs it force add it before finishing it. + self.add_gc_method(); + } + unsafe { + lua_pushvalue(self.vm.as_ptr(), -1); + lua_setfield(self.vm.as_ptr(), -2, c"__index".as_ptr()); + // Pop the userdata metatable from the stack. + lua_settop(self.vm.as_ptr(), -2); + } + } +} diff --git a/core/src/vm/userdata/error.rs b/core/src/vm/userdata/error.rs new file mode 100644 index 0000000..1b4dcba --- /dev/null +++ b/core/src/vm/userdata/error.rs @@ -0,0 +1,43 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use bp3d_util::simple_error; +use std::ffi::CStr; + +simple_error! { + pub Error { + ArgsEmpty => "no arguments specified in function, please add at least one argument matching the type of self", + MutViolation(&'static CStr) => "violation of the unique type rule for mutable method {:?}", + Gc => "__gc meta-method is reserved for internal use, if you need Vm access in drop, please use LuaDrop", + Index => "__index meta-method is required to be surrendered to luaL_newmetatable, it is impossible to bind custom code to __index", + Metatable => "__metatable is set for security reasons and cannot be altered", + MultiValueField => "multi-value fields are not supported", + AlreadyRegistered(&'static CStr) => "class name {:?} has already been registered", + Alignment(usize) => "too strict alignment required ({} bytes), max is 8 bytes" + } +} diff --git a/core/src/vm/userdata/interface.rs b/core/src/vm/userdata/interface.rs new file mode 100644 index 0000000..49dc135 --- /dev/null +++ b/core/src/vm/userdata/interface.rs @@ -0,0 +1,60 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::vm::userdata::{core::Registry, Error}; +use crate::vm::Vm; +use std::borrow::Cow; +use std::ffi::CStr; + +/// This trait represents all types of UserData. An UserData is a type with a maximum alignment of 8 +/// with its memory tied to the Lua GC. +pub trait UserData: Sized { + const CLASS_NAME: &'static CStr; + + fn register(registry: &Registry) -> Result<(), Error>; +} + +/// This trait represents an UserData which is never borrowed mutably (excluding interior mutability +/// patterns). +/// +/// # Safety +/// +/// This is UB to implement on UserData types which may be borrowed mutably. +pub unsafe trait UserDataImmutable: UserData {} + +pub trait LuaDrop { + fn lua_drop(&self, vm: &Vm); +} + +pub trait AddGcMethod { + fn add_gc_method(&self, reg: &Registry); +} + +pub trait NameConvert { + fn name_convert(&self, name: &'static CStr) -> Cow<'static, CStr>; +} diff --git a/core/src/vm/userdata/mod.rs b/core/src/vm/userdata/mod.rs new file mode 100644 index 0000000..d8f434a --- /dev/null +++ b/core/src/vm/userdata/mod.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod any; +pub mod case; +pub mod core; +mod error; +mod interface; + +pub use any::AnyUserData; +pub use error::Error; +pub use interface::*; diff --git a/core/src/vm/util.rs b/core/src/vm/util.rs new file mode 100644 index 0000000..30d80b8 --- /dev/null +++ b/core/src/vm/util.rs @@ -0,0 +1,77 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_error, lua_pushlstring, State}; +use std::error::Error; + +#[derive(Debug, PartialEq, Eq)] +pub enum TypeName { + Some(&'static str), + None, +} + +pub trait LuaType { + /// Returns the closest rust type matching this lua value. + fn lua_type() -> Vec { + vec![TypeName::Some(std::any::type_name::())] + } +} + +impl LuaType for Option { + fn lua_type() -> Vec { + let mut v = T::lua_type(); + v.push(TypeName::None); + v + } +} + +/// Converts a Rust error to a Lua error. This function does not return as it unwinds using luajit. +/// +/// # Arguments +/// +/// * `l`: the lua State on which to raise the lua exception. +/// * `error`: the Rust error to be converted. +/// +/// returns: ! +/// +/// # Safety +/// +/// It is UB to call this function outside a lua [CFunction](crate::ffi::lua::CFunction). +pub unsafe fn lua_rust_error(l: State, error: E) -> ! { + // At this point the function is assumed to be a non-POF (error and String). + let s = format!("rust error: {}", error); + lua_pushlstring(l, s.as_ptr() as _, s.len()); + // Drop both the error and the error string. + // Very important as lua_error does not return. + drop(error); + drop(s); + // Now the function should be back what Rust calls a POF. + lua_error(l); + // If this is reached, then lua_error has silently failed. + std::unreachable!() +} diff --git a/core/src/vm/value/any.rs b/core/src/vm/value/any.rs new file mode 100644 index 0000000..4083df2 --- /dev/null +++ b/core/src/vm/value/any.rs @@ -0,0 +1,245 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_pushnil, lua_toboolean, lua_tonumber, lua_type, Type}; +use crate::util::core::SimpleDrop; +use crate::vm::error::{Error, TypeError}; +use crate::vm::function::{FromParam, IntoParam}; +use crate::vm::table::Table; +use crate::vm::thread::Thread; +use crate::vm::userdata::AnyUserData; +use crate::vm::util::{lua_rust_error, LuaType}; +use crate::vm::value::function::Function; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; +use std::fmt::Display; +use std::str::FromStr; + +#[derive(Debug, PartialEq, Clone)] +pub enum AnyValue<'a> { + None, + Nil, + Number(f64), + Boolean(bool), + String(&'a str), + Buffer(&'a [u8]), + Function(Function<'a>), + Table(Table<'a>), + UserData(AnyUserData<'a>), + Thread(Thread<'a>), +} + +impl Eq for AnyValue<'_> {} + +impl Display for AnyValue<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AnyValue::None => f.write_str(""), + AnyValue::Nil => f.write_str("nil"), + AnyValue::Number(v) => write!(f, "{}", v), + AnyValue::Boolean(v) => write!(f, "{}", v), + AnyValue::String(v) => write!(f, "{}", v), + AnyValue::Buffer(v) => write!(f, "{:?}", v), + AnyValue::Function(v) => write!(f, "{}", v), + AnyValue::Table(v) => write!(f, "{}", v), + AnyValue::UserData(v) => write!(f, "{}", v), + AnyValue::Thread(v) => write!(f, "{}", v), + } + } +} + +impl AnyValue<'_> { + pub fn ty(&self) -> Type { + match self { + AnyValue::None => Type::None, + AnyValue::Nil => Type::Nil, + AnyValue::Number(_) => Type::Number, + AnyValue::Boolean(_) => Type::Boolean, + AnyValue::String(_) => Type::String, + AnyValue::Buffer(_) => Type::String, + AnyValue::Function(_) => Type::Function, + AnyValue::Table(_) => Type::Table, + AnyValue::UserData(_) => Type::Userdata, + AnyValue::Thread(_) => Type::Thread, + } + } + + pub fn to_number(&self) -> Result { + match self { + AnyValue::Number(v) => Ok(*v), + AnyValue::String(v) => { + crate::ffi::lua::RawNumber::from_str(v).map_err(|_| Error::ParseFloat) + } + _ => Err(Error::Type(TypeError { + expected: Type::Number, + actual: self.ty(), + })), + } + } + + pub fn to_integer(&self) -> Result { + match self { + AnyValue::Number(v) => Ok(*v as _), + AnyValue::String(v) => { + crate::ffi::lua::RawInteger::from_str(v).map_err(|_| Error::ParseInt) + } + _ => Err(Error::Type(TypeError { + expected: Type::Number, + actual: self.ty(), + })), + } + } +} + +unsafe impl IntoParam for AnyValue<'_> { + fn into_param(self, vm: &Vm) -> u16 { + match self { + AnyValue::None => 0, + AnyValue::Nil => { + unsafe { lua_pushnil(vm.as_ptr()) }; + 1 + } + AnyValue::Number(v) => v.into_lua(vm), + AnyValue::Boolean(v) => v.into_lua(vm), + AnyValue::String(v) => v.into_lua(vm), + AnyValue::Buffer(v) => v.into_lua(vm), + AnyValue::Function(v) => v.into_lua(vm), + AnyValue::Table(v) => v.into_lua(vm), + AnyValue::UserData(_) => 0, + AnyValue::Thread(_) => 0, + } + } +} + +impl<'a> FromLua<'a> for AnyValue<'a> { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + Self::from_lua(vm, index).unwrap_unchecked() + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let ty = unsafe { lua_type(vm.as_ptr(), index) }; + match ty { + Type::None => Ok(AnyValue::None), + Type::Nil => Ok(AnyValue::Nil), + Type::Boolean => { + let value = unsafe { lua_toboolean(vm.as_ptr(), index) }; + Ok(AnyValue::Boolean(value == 1)) + } + Type::LightUserdata => Err(Error::UnsupportedType(ty)), + Type::Number => { + let value = unsafe { lua_tonumber(vm.as_ptr(), index) }; + Ok(AnyValue::Number(value)) + } + Type::String => { + let buffer: &[u8] = unsafe { FromLua::from_lua_unchecked(vm, index) }; + match std::str::from_utf8(buffer) { + Ok(s) => Ok(AnyValue::String(s)), + Err(_) => Ok(AnyValue::Buffer(buffer)), + } + } + Type::Table => Ok(unsafe { AnyValue::Table(FromLua::from_lua_unchecked(vm, index)) }), + Type::Function => { + Ok(unsafe { AnyValue::Function(FromLua::from_lua_unchecked(vm, index)) }) + } + Type::Userdata => { + Ok(unsafe { AnyValue::UserData(FromLua::from_lua_unchecked(vm, index)) }) + } + Type::Thread => Ok(unsafe { AnyValue::Thread(FromLua::from_lua_unchecked(vm, index)) }), + } + } +} + +unsafe impl SimpleDrop for AnyValue<'_> {} + +impl LuaType for AnyValue<'_> {} + +impl<'a> FromParam<'a> for AnyValue<'a> { + #[inline(always)] + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + match FromLua::from_lua(vm, index) { + Ok(v) => v, + Err(e) => lua_rust_error(vm.as_ptr(), e), + } + } + + #[inline(always)] + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } +} + +/// A marker struct to run lua code which may return any number of values on the stack. +pub struct AnyParam; + +impl FromLua<'_> for AnyParam { + #[inline(always)] + unsafe fn from_lua_unchecked(_: &Vm, _: i32) -> Self { + AnyParam + } + + #[inline(always)] + fn from_lua(_: &Vm, _: i32) -> crate::vm::Result { + Ok(AnyParam) + } + + #[inline(always)] + fn num_values() -> i16 { + -1 + } +} + +/// A raw primitive to return arbitrary count of values from a C function. +pub struct UncheckedAnyReturn(u16); + +impl UncheckedAnyReturn { + /// Construct a [UncheckedAnyReturn]. + /// + /// # Panic + /// + /// This function panics when the count of arguments is greater than the lua stack size itself. + /// + /// # Safety + /// + /// It is UB to run any operation which may alter the lua stack after constructing this + /// primitive. + pub unsafe fn new(vm: &Vm, count: u16) -> Self { + let top = vm.top(); + if count > top as _ { + panic!() + } + UncheckedAnyReturn(count) + } +} + +unsafe impl IntoParam for UncheckedAnyReturn { + #[inline(always)] + fn into_param(self, _: &Vm) -> u16 { + self.0 + } +} diff --git a/core/src/vm/value/core.rs b/core/src/vm/value/core.rs new file mode 100644 index 0000000..fd3ebd3 --- /dev/null +++ b/core/src/vm/value/core.rs @@ -0,0 +1,254 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::luaL_testudata; +use crate::ffi::lua::{ + lua_settop, lua_toboolean, lua_tointeger, lua_tolstring, lua_tonumber, lua_touserdata, + lua_type, Type, +}; +use crate::vm::error::{Error, TypeError}; +use crate::vm::function::IntoParam; +use crate::vm::userdata::UserDataImmutable; +use crate::vm::value::util::ensure_type_equals; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; + +impl<'a> FromLua<'a> for &'a str { + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + let mut len: usize = 0; + let s = lua_tolstring(vm.as_ptr(), index, &mut len as _); + let slice = std::slice::from_raw_parts(s as _, len); + std::str::from_utf8_unchecked(slice) + } + + fn from_lua(vm: &Vm, index: i32) -> crate::vm::Result { + let l = vm.as_ptr(); + unsafe { + let ty = lua_type(l, index); + match ty { + Type::String => { + let mut len: usize = 0; + let s = lua_tolstring(l, index, &mut len as _); + let slice = std::slice::from_raw_parts(s as _, len); + std::str::from_utf8(slice).map_err(|e| Error::InvalidUtf8(e.into())) + } + _ => Err(Error::Type(TypeError { + expected: Type::String, + actual: ty, + })), + } + } + } +} + +impl<'a> FromLua<'a> for &'a [u8] { + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + let mut len: usize = 0; + let str = lua_tolstring(vm.as_ptr(), index, &mut len as _); + let slice = std::slice::from_raw_parts(str as *const u8, len); + slice + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let l = vm.as_ptr(); + unsafe { + let ty = lua_type(l, index); + match ty { + Type::String => { + let mut len: usize = 0; + let s = lua_tolstring(l, index, &mut len as _); + let slice = std::slice::from_raw_parts(s as *const u8, len); + Ok(slice) + } + _ => Err(Error::Type(TypeError { + expected: Type::String, + actual: ty, + })), + } + } + } +} + +macro_rules! impl_from_lua { + ($t: ty, $expected: ident, $func: ident, $($ret: tt)*) => { + impl FromLua<'_> for $t { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &Vm, index: i32) -> Self { + $func(vm.as_ptr(), index) $($ret)* + } + + fn from_lua(vm: &Vm, index: i32) -> crate::vm::Result { + ensure_type_equals(vm, index, Type::$expected)?; + Ok(unsafe { $func(vm.as_ptr(), index) $($ret)* }) + } + } + }; +} + +#[cfg(target_pointer_width = "64")] +impl_from_lua!(i64, Number, lua_tointeger, as _); + +#[cfg(target_pointer_width = "64")] +impl_from_lua!(u64, Number, lua_tointeger, as _); + +impl_from_lua!(i8, Number, lua_tointeger, as _); +impl_from_lua!(u8, Number, lua_tointeger, as _); +impl_from_lua!(i16, Number, lua_tointeger, as _); +impl_from_lua!(u16, Number, lua_tointeger, as _); +impl_from_lua!(i32, Number, lua_tointeger, as _); +impl_from_lua!(u32, Number, lua_tointeger, as _); + +impl_from_lua!(f32, Number, lua_tonumber, as _); +impl_from_lua!(f64, Number, lua_tonumber, as _); + +impl_from_lua!(bool, Boolean, lua_toboolean, == 1); + +unsafe impl IntoLua for T { + #[inline(always)] + fn into_lua(self, vm: &Vm) -> u16 { + self.into_param(vm) + } +} + +impl FromLua<'_> for () { + #[inline(always)] + unsafe fn from_lua_unchecked(_: &'_ Vm, _: i32) -> Self {} + + #[inline(always)] + fn from_lua(_vm: &Vm, _: i32) -> crate::vm::Result<()> { + Ok(()) + } + + #[inline(always)] + fn num_values() -> i16 { + 0 + } +} + +impl<'a, T: UserDataImmutable> FromLua<'a> for &'a T { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + &*(lua_touserdata(vm.as_ptr(), index) as *const T) + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let this_ptr = + unsafe { luaL_testudata(vm.as_ptr(), index, T::CLASS_NAME.as_ptr()) } as *const T; + if this_ptr.is_null() { + return Err(Error::Type(TypeError { + expected: Type::Userdata, + actual: unsafe { lua_type(vm.as_ptr(), index) }, + })); + } + Ok(unsafe { &*this_ptr }) + } +} + +macro_rules! count_tts { + () => {0}; + ($_head:tt $($tail:tt)*) => {1 + count_tts!($($tail)*)}; +} + +macro_rules! impl_from_lua_tuple { + ($($name: ident: $name2: ident),*) => { + impl<'a, $($name: FromLua<'a>),*> FromLua<'a> for ($($name),*) { + #[inline(always)] + fn num_values() -> i16 { + count_tts!($($name)*) + } + + unsafe fn from_lua_unchecked(vm: &'a Vm, mut index: i32) -> Self { + impl_from_lua_tuple!(_from_lua_unchecked vm, index, $($name2: $name),*); + ($($name2),*) + } + + fn from_lua(vm: &'a Vm, mut index: i32) -> crate::vm::Result<($($name),*)> { + impl_from_lua_tuple!(_from_lua vm, index, $($name2: $name),*); + Ok(($($name2),*)) + } + } + }; + + (_from_lua_unchecked $vm: ident, $index: ident, $name2: ident: $name: ident) => { + let $name2: $name = FromLua::from_lua_unchecked($vm, $index); + }; + + (_from_lua_unchecked $vm: ident, $index: ident, $name2: ident: $name: ident, $($name3: ident: $name4: ident),*) => { + let $name2: $name = FromLua::from_lua_unchecked($vm, $index); + $index += 1; + impl_from_lua_tuple!(_from_lua_unchecked $vm, $index, $($name3: $name4),*); + }; + + (_from_lua $vm: ident, $index: ident, $name2: ident: $name: ident) => { + let $name2: $name = FromLua::from_lua($vm, $index)?; + }; + + (_from_lua $vm: ident, $index: ident, $name2: ident: $name: ident, $($name3: ident: $name4: ident),*) => { + let $name2: $name = FromLua::from_lua($vm, $index)?; + $index += 1; + impl_from_lua_tuple!(_from_lua $vm, $index, $($name3: $name4),*); + }; +} + +impl_from_lua_tuple!(T: t, T1: t1); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3, T4: t4); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3, T4: t4, T5: t5); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3, T4: t4, T5: t5, T6: t6); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3, T4: t4, T5: t5, T6: t6, T7: t7); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3, T4: t4, T5: t5, T6: t6, T7: t7, T8: t8); +impl_from_lua_tuple!(T: t, T1: t1, T2: t2, T3: t3, T4: t4, T5: t5, T6: t6, T7: t7, T8: t8, T9: t9); + +impl<'a, T: FromLua<'a>> FromLua<'a> for Option { + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + let ty = unsafe { lua_type(vm.as_ptr(), index) }; + if ty == Type::Nil { + // Clear the nil value at the top of the stack. + if index == -1 { + unsafe { lua_settop(vm.as_ptr(), -2) }; + } + None + } else { + Some(FromLua::from_lua_unchecked(vm, index)) + } + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let ty = unsafe { lua_type(vm.as_ptr(), index) }; + if ty == Type::Nil { + // Clear the nil value at the top of the stack. + if index == -1 { + unsafe { lua_settop(vm.as_ptr(), -2) }; + } + Ok(None) + } else { + Ok(Some(FromLua::from_lua(vm, index)?)) + } + } +} diff --git a/core/src/vm/value/function.rs b/core/src/vm/value/function.rs new file mode 100644 index 0000000..4f725b7 --- /dev/null +++ b/core/src/vm/value/function.rs @@ -0,0 +1,157 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::luaL_checktype; +use crate::ffi::lua::{lua_pushvalue, lua_topointer, Type}; +use crate::util::core::SimpleDrop; +use crate::vm::core::util::{pcall, push_error_handler}; +use crate::vm::function::{FromParam, IntoParam}; +use crate::vm::registry::{FromIndex, Set}; +use crate::vm::util::LuaType; +use crate::vm::value::util::ensure_type_equals; +use crate::vm::value::{FromLua, IntoLua}; +use crate::vm::Vm; +use std::fmt::{Debug, Display}; + +pub struct Function<'a> { + vm: &'a Vm, + index: i32, +} + +impl Clone for Function<'_> { + fn clone(&self) -> Self { + unsafe { lua_pushvalue(self.vm.as_ptr(), self.index) }; + Function { + vm: self.vm, + index: self.vm.top(), + } + } +} + +impl PartialEq for Function<'_> { + fn eq(&self, other: &Self) -> bool { + let a = unsafe { lua_topointer(self.vm.as_ptr(), self.index) }; + let b = unsafe { lua_topointer(other.vm.as_ptr(), other.index) }; + a == b + } +} + +impl Eq for Function<'_> {} + +impl Display for Function<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "function@{:X}", + unsafe { lua_topointer(self.vm.as_ptr(), self.index) } as usize + ) + } +} + +impl Debug for Function<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "LuaFunction({:?})", self.index) + } +} + +unsafe impl SimpleDrop for Function<'_> {} + +impl LuaType for Function<'_> {} + +impl<'a> FromParam<'a> for Function<'a> { + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + unsafe { luaL_checktype(vm.as_ptr(), index, Type::Function) }; + Function { vm, index } + } + + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + FromLua::from_lua(vm, index).ok() + } +} + +unsafe impl IntoParam for Function<'_> { + fn into_param(self, vm: &Vm) -> u16 { + unsafe { lua_pushvalue(vm.as_ptr(), self.index) }; + 1 + } +} + +impl Function<'_> { + pub fn call<'b, R: FromLua<'b>>(&'b self, value: impl IntoLua) -> crate::vm::Result { + let pos = unsafe { push_error_handler(self.vm.as_ptr()) }; + unsafe { + lua_pushvalue(self.vm.as_ptr(), self.index); + } + let num_values = value.into_lua(self.vm); + unsafe { pcall(self.vm, num_values as _, R::num_values() as _, pos)? }; + R::from_lua(self.vm, -(R::num_values() as i32)) + } + + /// Returns the absolute index of this function on the Lua stack. + #[inline(always)] + pub fn index(&self) -> i32 { + self.index + } +} + +impl<'a> FromLua<'a> for Function<'a> { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Function<'a> { + Function { + vm, + index: vm.get_absolute_index(index), + } + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + ensure_type_equals(vm, index, Type::Function)?; + Ok(Function { + vm, + index: vm.get_absolute_index(index), + }) + } +} + +impl crate::vm::registry::Value for crate::vm::registry::types::Function { + type Value<'a> = Function<'a>; + + #[inline(always)] + unsafe fn from_registry(vm: &Vm, index: i32) -> Self::Value<'_> { + unsafe { Function::from_lua_unchecked(vm, index) } + } + + #[inline(always)] + fn push_registry(value: Self::Value<'_>) -> R { + unsafe { R::from_index(value.vm, value.index()) } + } + + #[inline(always)] + unsafe fn set_registry(key: &impl Set, value: Self::Value<'_>) { + key.set(value.vm, value.index()) + } +} diff --git a/core/src/vm/value/interface.rs b/core/src/vm/value/interface.rs new file mode 100644 index 0000000..140f273 --- /dev/null +++ b/core/src/vm/value/interface.rs @@ -0,0 +1,89 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::vm::Vm; + +// WTF this is broken, if you do not indent then it becomes unreadable. I chose readability over +// un-readability. +#[allow(clippy::doc_overindented_list_items)] +pub trait FromLua<'a>: Sized { + /// Reads the value at the specified index in the given [Vm]. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to read from. + /// * `index`: the index at which to try reading the value from. The index does not have to be + /// absolute. + /// + /// returns: Result + /// + /// # Safety + /// + /// This function assumes the type of the value at index `index` is already of the expected type, + /// if not, calling this function is UB. + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self; + + /// Attempt to read the value at the specified index in the given [Vm]. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to read from. + /// * `index`: the index at which to try reading the value from. The index does not have to be + /// absolute. + /// + /// returns: Result + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result; + + /// Returns the number of values to be expected on the lua stack, after reading this value. + #[inline(always)] + fn num_values() -> i16 { + 1 + } +} + +/// This trait represents a value convertible to lua outside Rust function calls. For lua values +/// returned by Rust functions, see [IntoParam](crate::vm::function::IntoParam). +/// +/// # Safety +/// +/// When implementing this trait, ensure that the number returned by +/// [into_lua](IntoLua::into_lua) is EXACTLY equal to the number of values pushed onto the lua +/// stack. If more or fewer than advertised values exists on the stack after the call then the impl +/// is considered UB. +pub unsafe trait IntoLua: Sized { + /// Attempt to push self onto the top of the stack in the given [Vm]. + /// + /// Returns the number values pushed into the lua stack. + /// + /// # Arguments + /// + /// * `vm`: the [Vm] to push into. + /// + /// returns: u16 number of elements pushed onto the Lua stack. + fn into_lua(self, vm: &Vm) -> u16; +} diff --git a/core/src/vm/value/mod.rs b/core/src/vm/value/mod.rs new file mode 100644 index 0000000..51cffa6 --- /dev/null +++ b/core/src/vm/value/mod.rs @@ -0,0 +1,36 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod any; +mod core; +mod function; +mod interface; +pub mod types; +pub mod util; + +pub use interface::*; diff --git a/core/src/vm/value/types.rs b/core/src/vm/value/types.rs new file mode 100644 index 0000000..61195cf --- /dev/null +++ b/core/src/vm/value/types.rs @@ -0,0 +1,130 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::laux::{luaL_checkinteger, luaL_checknumber}; +use crate::ffi::lua::{ + lua_isnumber, lua_tointeger, lua_tonumber, lua_type, RawInteger, RawNumber, Type, +}; +use crate::util::core::SimpleDrop; +use crate::vm::error::{Error, TypeError}; +use crate::vm::function::FromParam; +use crate::vm::util::LuaType; +use crate::vm::value::FromLua; +use crate::vm::Vm; + +pub use super::function::Function; + +#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] +pub struct Number(pub RawNumber); + +unsafe impl SimpleDrop for Number {} + +impl LuaType for Number {} + +#[derive(Copy, Clone, PartialOrd, PartialEq, Debug, Eq, Ord, Hash)] +pub struct Integer(pub RawInteger); + +unsafe impl SimpleDrop for Integer {} + +impl LuaType for Integer {} + +impl<'a> FromParam<'a> for Number { + #[inline(always)] + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + Self(luaL_checknumber(vm.as_ptr(), index)) + } + + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + let l = vm.as_ptr(); + unsafe { + if lua_isnumber(l, index) == 1 { + Some(Self(lua_tonumber(l, index))) + } else { + None + } + } + } +} + +impl<'a> FromLua<'a> for Number { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + Self(lua_tonumber(vm.as_ptr(), index)) + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let l = vm.as_ptr(); + unsafe { + if lua_isnumber(l, index) != 1 { + return Err(Error::Type(TypeError { + expected: Type::Number, + actual: lua_type(l, index), + })); + } + Ok(Self(lua_tonumber(l, index))) + } + } +} + +impl<'a> FromParam<'a> for Integer { + #[inline(always)] + unsafe fn from_param(vm: &'a Vm, index: i32) -> Self { + Self(luaL_checkinteger(vm.as_ptr(), index)) + } + + fn try_from_param(vm: &'a Vm, index: i32) -> Option { + let l = vm.as_ptr(); + unsafe { + if lua_isnumber(l, index) == 1 { + Some(Self(lua_tointeger(l, index))) + } else { + None + } + } + } +} + +impl<'a> FromLua<'a> for Integer { + #[inline(always)] + unsafe fn from_lua_unchecked(vm: &'a Vm, index: i32) -> Self { + Self(lua_tointeger(vm.as_ptr(), index)) + } + + fn from_lua(vm: &'a Vm, index: i32) -> crate::vm::Result { + let l = vm.as_ptr(); + unsafe { + if lua_isnumber(l, index) != 1 { + return Err(Error::Type(TypeError { + expected: Type::Number, + actual: lua_type(l, index), + })); + } + Ok(Self(lua_tointeger(l, index))) + } + } +} diff --git a/core/src/vm/value/util.rs b/core/src/vm/value/util.rs new file mode 100644 index 0000000..b238a15 --- /dev/null +++ b/core/src/vm/value/util.rs @@ -0,0 +1,80 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::ffi::lua::{lua_pushnil, lua_pushvalue, lua_replace, lua_settop, Type}; +use crate::vm::error::{Error, TypeError}; +use crate::vm::value::IntoLua; +use crate::vm::Vm; + +/// Ensures the given lua value at index is of a specified type. +#[inline(always)] +pub fn ensure_type_equals(vm: &Vm, index: i32, expected: Type) -> crate::vm::Result<()> { + let ty = unsafe { crate::ffi::lua::lua_type(vm.as_ptr(), index) }; + if ty == expected { + //FIXME: likely branch + Ok(()) + } else { + Err(Error::Type(TypeError { + expected, + actual: ty, + })) + } +} + +/// Ensures the given lua value at index is at the top of the stack. +/// If the value at index is not at the top of the stack, this function moves it to the top and +/// replaces the original index by a nil value. +#[inline(always)] +pub fn ensure_value_top(vm: &Vm, index: i32) { + let index = vm.get_absolute_index(index); + if index != vm.top() { + let l = vm.as_ptr(); + unsafe { + lua_pushvalue(l, index); + lua_pushnil(l); + lua_replace(l, index); // Replace the value at index by a nil. + } + } +} + +/// Ensures a single value is pushed onto the lua stack, this function automatically reverts the +/// stack if value pushed more than 1 element onto the stack. +/// +/// # Arguments +/// +/// * `vm`: the vm to operate on. +/// * `value`: the value to be placed on the lua stack. +pub fn ensure_single_into_lua(vm: &Vm, value: impl IntoLua) -> crate::vm::Result<()> { + let nums = value.into_lua(vm); + if nums != 1 { + // Clear the stack. + unsafe { lua_settop(vm.as_ptr(), -(nums as i32) - 1) }; + return Err(Error::MultiValue); + } + Ok(()) +} diff --git a/core/tests/lua/basic.lua b/core/tests/lua/basic.lua new file mode 100644 index 0000000..02d21cc --- /dev/null +++ b/core/tests/lua/basic.lua @@ -0,0 +1,5 @@ +function main() + error("nope") +end + +return 1 + main() diff --git a/core/tests/lua/broken.lua b/core/tests/lua/broken.lua new file mode 100644 index 0000000..62e484b --- /dev/null +++ b/core/tests/lua/broken.lua @@ -0,0 +1,4 @@ +function bro + end + +return + diff --git a/core/tests/lua/class.lua b/core/tests/lua/class.lua new file mode 100644 index 0000000..22a9b08 --- /dev/null +++ b/core/tests/lua/class.lua @@ -0,0 +1,58 @@ +--- @param name string +--- @param parent table +function AbstractClass(name, parent) + local class = {} + if not class.init then + class.init = function(_) end + end + class.__name = name + class.__index = class + if parent then + setmetatable(class, parent) + end + return class +end + +--- @param name string +--- @param parent table +function Class(name, parent) + local class = {} + if not class.init then + class.init = function(_) end + end + class.__name = name + class.new = function(...) + local obj = {} + setmetatable(obj, class) + obj:init(...) + return obj + end + class.__index = class + if parent then + setmetatable(class, parent) + end + return class +end + +--- @class Parent +local Parent = AbstractClass("Parent") + +function Parent:value() + return 42 +end + +--- @class Child +local Child = Class("Child", Parent) + +function Child:init(a) + Parent.init(self) + self._a = a +end + +function Child:value2() + return self:value() + self._a +end + +local obj = Child.new(42) +assert(obj:value2() == 84) +assert(obj:value() == 42) diff --git a/core/tests/test_vm_backtrace.rs b/core/tests/test_vm_backtrace.rs new file mode 100644 index 0000000..4ddd9ee --- /dev/null +++ b/core/tests/test_vm_backtrace.rs @@ -0,0 +1,76 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::decl_lib_func; +use bp3d_lua::vm::function::types::RFunction; +use bp3d_lua::vm::value::types::Function; +use bp3d_lua::vm::RootVm; +use bp3d_util::simple_error; + +simple_error! { + pub Error { + Useless => "useless function called" + } +} + +decl_lib_func! { + fn error_func() -> Result<(), Error> { + Err(Error::Useless) + } +} + +#[test] +fn test_vm_backtrace() { + let vm = RootVm::new(); + let top = vm.top(); + vm.set_global(c"error_func", RFunction::wrap(error_func)) + .unwrap(); + vm.run_code::<()>( + c" + local function raise() + error_func() + end + + local function a() + raise() + end + + function main() + a() + end + ", + ) + .unwrap(); + let func: Function = vm.get_global(c"main").unwrap(); + let err = func.call::<()>(()).unwrap_err().into_runtime().unwrap(); + assert_eq!(err.msg(), "rust error: useless function called"); + assert_eq!(err.backtrace(), "rust error: useless function called\nstack traceback:\n\t[C]: in function 'error_func'\n\t[string \"...\"]:3: in function 'raise'\n\t[string \"...\"]:7: in function 'a'\n\t[string \"...\"]:11: in function <[string \"...\"]:10>"); + assert_eq!(vm.top(), top + 1); +} diff --git a/core/tests/test_vm_closures.rs b/core/tests/test_vm_closures.rs new file mode 100644 index 0000000..5e0bf9c --- /dev/null +++ b/core/tests/test_vm_closures.rs @@ -0,0 +1,146 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(all(feature = "root-vm", feature = "util-namespace"))] + +use bp3d_lua::decl_closure; +use bp3d_lua::util::Namespace; +use bp3d_lua::vm::closure::context::{CellMut, ContextMut}; +use bp3d_lua::vm::closure::types::RClosure; +use bp3d_lua::vm::RootVm; + +struct TestContext { + value: i32, + value3: Vec, +} + +decl_closure! { + fn context_set_value |ctx: ContextMut| (val: i32) -> () { + let mut ctx = ctx; + ctx.value = val; + } +} + +decl_closure! { + fn context_push |ctx: ContextMut| (val: u64) -> () { + let mut ctx = ctx; + ctx.value3.push(val); + } +} + +decl_closure! { + fn context_pop |ctx: ContextMut| () -> Option { + let mut ctx = ctx; + ctx.value3.pop() + } +} + +decl_closure! { + fn test |upvalue: &str| (val: f32) -> String { + format!("{}: {}", upvalue, val) + } +} + +#[test] +fn test_vm_fast_closure() { + let vm = RootVm::new(); + let top = vm.top(); + vm.set_global(c"test", test("this is a test")).unwrap(); + assert_eq!(top, vm.top()); + let s: &str = vm.run_code(c"return test(42.42)").unwrap(); + assert_eq!(s, "this is a test: 42.42"); +} + +#[test] +fn test_vm_rust_closure() { + let mut vm = RootVm::new(); + let top = vm.top(); + let closure = RClosure::from_rust(&mut vm, |val: f32| format!("this is a test: {}", val)); + vm.set_global(c"test", closure).unwrap(); + assert_eq!(top, vm.top()); + let s: &str = vm.run_code(c"return test(42.42)").unwrap(); + assert_eq!(s, "this is a test: 42.42"); +} + +#[test] +fn test_vm_context() { + let vm = RootVm::new(); + let top = vm.top(); + let ctx = ContextMut::new(&vm); + { + let mut namespace = Namespace::new(&vm, "context").unwrap(); + namespace + .add([ + ("push", context_push(ctx)), + ("pop", context_pop(ctx)), + ("set_value", context_set_value(ctx)), + ]) + .unwrap(); + } + assert_eq!(top, vm.top()); + let res = vm.run_code::<()>(c"context.set_value(42)"); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err().into_runtime().unwrap().msg(), + "[string \"context.set_value(42)\"]:1: Context is not available in this function." + ); + let mut obj = TestContext { + value: 0, + value3: vec![], + }; + let mut cell = CellMut::new(ctx); + { + let _obj = cell.bind(&mut obj); + vm.run_code::<()>(c"context.set_value(42)").unwrap(); + } + let res = vm.run_code::<()>(c"context.set_value(84)"); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err().into_runtime().unwrap().msg(), + "[string \"context.set_value(84)\"]:1: Context is not available in this function." + ); + assert_eq!(obj.value, 42); + { + let _obj = cell.bind(&mut obj); + vm.run_code::<()>(c"assert(context.pop() == nil)").unwrap(); + vm.run_code::<()>(c"context.push(1)").unwrap(); + vm.run_code::<()>(c"context.push(2)").unwrap(); + vm.run_code::<()>(c"context.push(3)").unwrap(); + } + assert_eq!(obj.value3.len(), 3); + { + let _obj = cell.bind(&mut obj); + vm.run_code::<()>(c"assert(context.pop() == 3)").unwrap(); + vm.run_code::<()>(c"assert(context.pop() == 2)").unwrap(); + vm.run_code::<()>(c"assert(context.pop() == 1)").unwrap(); + vm.run_code::<()>(c"assert(context.pop() == nil)").unwrap(); + vm.run_code::<()>(c"assert(context.pop() == nil)").unwrap(); + } + assert_eq!(obj.value3.len(), 0); + assert_eq!(top, vm.top()); +} diff --git a/core/tests/test_vm_custom_structs.rs b/core/tests/test_vm_custom_structs.rs new file mode 100644 index 0000000..46bf5c2 --- /dev/null +++ b/core/tests/test_vm_custom_structs.rs @@ -0,0 +1,118 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::decl_lib_func; +use bp3d_lua::vm::function::types::RFunction; +use bp3d_lua::vm::RootVm; +use bp3d_lua_codegen::LuaType; +use bp3d_lua_codegen::{FromParam, IntoParam}; + +#[derive(FromParam, LuaType, IntoParam)] +struct Test1<'a>(&'a str, i32); + +#[derive(FromParam, LuaType, IntoParam)] +struct Test2<'a> { + name: &'a str, + value: i32, +} + +#[derive(FromParam, LuaType, IntoParam)] +struct TestStatic { + value1: f32, + value2: i32, +} + +decl_lib_func! { + fn test(test1: Test1, test2: Test2, st: TestStatic) -> String { + format!("{} {}: {}, {}, (v1: {}, v2: {})", test1.0, test2.name, test1.1, test2.value, st.value1, st.value2) + } +} + +decl_lib_func! { + fn test2(name: &str) -> Test2 { + Test2 { name, value: 42 } + } +} + +decl_lib_func! { + fn test3<'a>(name: &'a str, name2: &str) -> Test2<'a> { + println!("{}", name2); + Test2 { name, value: 42 } + } +} + +#[test] +fn basic() { + let vm = RootVm::new(); + let top = vm.top(); + vm.set_global(c"test", RFunction::wrap(test)).unwrap(); + vm.set_global(c"test2", RFunction::wrap(test2)).unwrap(); + vm.set_global(c"test3", RFunction::wrap(test3)).unwrap(); + let out = vm + .run_code::<&str>( + c" + local test1 = { 'value', 42 } + local test2 = { name = 'of', value = 64 } + local st = { value1 = 42.42, value2 = 32 } + return test(test1, test2, st) + ", + ) + .unwrap(); + assert_eq!(out, "value of: 42, 64, (v1: 42.42, v2: 32)"); + vm.set_global( + c"test", + Test2 { + name: "whatever", + value: 42, + }, + ) + .unwrap(); + let out = vm.run_code::<&str>(c"return test.name").unwrap(); + assert_eq!(out, "whatever"); + let out = vm.run_code::(c"return test.value").unwrap(); + assert_eq!(out, 42); + vm.run_code::<()>( + c" + local t2 = test2('test') + assert(t2.name == 'test') + assert(t2.value == 42) + ", + ) + .unwrap(); + vm.run_code::<()>( + c" + local t2 = test3('test42', 'test2') + assert(t2.name == 'test42') + assert(t2.value == 42) + ", + ) + .unwrap(); + assert_eq!(top + 3, vm.top()) +} diff --git a/core/tests/test_vm_destructor.rs b/core/tests/test_vm_destructor.rs new file mode 100644 index 0000000..2a9cb20 --- /dev/null +++ b/core/tests/test_vm_destructor.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::decl_lib_func; +use bp3d_lua::vm::function::types::RFunction; +use bp3d_lua::vm::RootVm; + +struct ValueWithDrop; +impl ValueWithDrop { + pub fn print(&self) { + println!("ValueWithDrop") + } +} +impl Drop for ValueWithDrop { + fn drop(&mut self) { + println!("Dropping!"); + } +} + +decl_lib_func! { + fn test_c_function(name: &str, value: f64) -> String { + let drop = ValueWithDrop; + drop.print(); + format!("Hello {} ({})", name, value) + } +} + +#[test] +fn test_vm_destructor() { + let mut vm = RootVm::new(); + vm.set_global(c"test_c_function", RFunction::wrap(test_c_function)) + .unwrap(); + let time = std::time::Instant::now(); + let res = vm.run_code::<&str>(c"return test_c_function('this is a test\\xFF', 0.42)"); + assert!(res.is_err()); + let err = res.unwrap_err().into_runtime().unwrap(); + assert_eq!( + err.msg(), + "rust error: invalid utf-8 sequence of 1 bytes from index 14" + ); + assert!(vm + .run_code::<&str>(c"return test_c_function('this is a test', 0.42)") + .is_ok()); + let s = vm + .run_code::<&str>(c"return test_c_function('this is a test', 0.42)") + .unwrap(); + assert_eq!(s, "Hello this is a test (0.42)"); + assert!(vm + .run_code::(c"return test_c_function('this is a test', 0.42)") + .is_err()); + vm.clear(); + let time = time.elapsed(); + println!("time: {:?}", time); +} diff --git a/core/tests/test_vm_functions.rs b/core/tests/test_vm_functions.rs new file mode 100644 index 0000000..f3ab19f --- /dev/null +++ b/core/tests/test_vm_functions.rs @@ -0,0 +1,86 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(all(feature = "root-vm", feature = "util-method"))] + +use bp3d_lua::util::LuaMethod; +use bp3d_lua::vm::table::Table; +use bp3d_lua::vm::value::types::Function; +use bp3d_lua::vm::RootVm; +use std::ffi::CStr; + +#[test] +fn test_vm_function_1_arg() { + let mut vm = RootVm::new(); + let top = vm.top(); + let f: Function = vm + .run_code(c"return function(value) return 'this is a test ' .. value end") + .unwrap(); + let str: &str = f.call(42.42).unwrap(); + assert_eq!(str, "this is a test 42.42"); + let str: &str = f.call(42).unwrap(); + assert_eq!(str, "this is a test 42"); + assert_eq!(vm.top(), top + 3); // Function + 2 results + vm.clear(); +} + +#[test] +fn test_vm_function_2_args() { + let mut vm = RootVm::new(); + let top = vm.top(); + let f: Function = vm.run_code(c"return function(value, value2) return 'this ' .. value .. ' is a test ' .. tostring(value2) end").unwrap(); + let str: &str = f.call((42.42, false)).unwrap(); + assert_eq!(str, "this 42.42 is a test false"); + let str: &str = f.call((42, true)).unwrap(); + assert_eq!(str, "this 42 is a test true"); + assert_eq!(vm.top(), top + 3); // Function + 2 results + vm.clear(); +} + +const METHODS: &CStr = c" +local obj = { ctx = 'this is a test' } + +function obj:greeting() + return 'Hello ' .. self.ctx +end + +return obj +"; + +#[test] +fn test_vm_function_method() { + let mut vm = RootVm::new(); + let top = vm.top(); + let obj: Table = vm.run_code(METHODS).unwrap(); + let method = LuaMethod::create(obj, c"greeting").unwrap(); + let str: &str = method.call(&vm, ()).unwrap(); + assert_eq!(str, "Hello this is a test"); + method.delete(&vm); + assert_eq!(vm.top(), top + 1); // 1 result + vm.clear(); +} diff --git a/core/tests/test_vm_interrupt.rs b/core/tests/test_vm_interrupt.rs new file mode 100644 index 0000000..204e63d --- /dev/null +++ b/core/tests/test_vm_interrupt.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "interrupt")] + +use bp3d_lua::libs::Lib; +use bp3d_lua::vm::core::interrupt; +use std::time::Duration; + +#[test] +fn test_vm_interrupt() { + let (signal, handle) = interrupt::spawn_interruptible(|vm| { + bp3d_lua::libs::os::Compat.register(vm).unwrap(); + // Run the malicious code. + vm.run_code::<()>( + c" + local tbl = {} + while (true) do + local time = os.date('!%x %H:%M:%S') + table.insert(tbl, time) + end + ", + ) + }); + // Give the chance to the thread to run and pump a bit of RAM. + std::thread::sleep(Duration::from_millis(500)); + signal.send(Duration::from_secs(2)).unwrap(); + let res = handle.join().unwrap(); + assert!(res.is_err()); +} diff --git a/core/tests/test_vm_jit_options.rs b/core/tests/test_vm_jit_options.rs new file mode 100644 index 0000000..e51a4b8 --- /dev/null +++ b/core/tests/test_vm_jit_options.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use bp3d_lua::vm::core::jit::{JitOptions, OptLevel}; +use bp3d_lua::vm::RootVm; + +#[test] +fn test_vm_get_jit_options() { + let vm = RootVm::new(); + let jit = JitOptions::get(&vm); + let optimizations = jit.opts(); + let cpu = jit.cpu(); + let is_on = jit.is_enabled(); + assert_eq!(is_on, true); + assert_eq!(jit.opt_level(), OptLevel::default()); + println!("{} {}", cpu, optimizations); +} + +#[test] +fn test_vm_set_jit_options() { + let mut vm = RootVm::new(); + let mut jit = JitOptions::get(&vm); + assert_eq!(jit.opt_level(), OptLevel::default()); + jit.set_opt_level(OptLevel::O0); + assert_eq!(jit.opt_level(), OptLevel::O0); + jit.apply(&mut vm); + let jit = JitOptions::get(&vm); + assert_eq!(jit.opt_level(), OptLevel::O0); +} + +#[test] +fn test_vm_disable_jit() { + let mut vm = RootVm::new(); + let mut jit = JitOptions::get(&vm); + assert!(jit.is_enabled()); + jit.disable(); + jit.apply(&mut vm); + let jit = JitOptions::get(&vm); + assert!(!jit.is_enabled()); +} diff --git a/core/tests/test_vm_libs.rs b/core/tests/test_vm_libs.rs new file mode 100644 index 0000000..b34b994 --- /dev/null +++ b/core/tests/test_vm_libs.rs @@ -0,0 +1,289 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(all(feature = "root-vm", feature = "libs"))] + +use bp3d_lua::libs::lua::{Lua, Module}; +use bp3d_lua::libs::util::Util; +use bp3d_lua::libs::Lib; +use bp3d_lua::vm::RootVm; + +#[test] +fn test_vm_lib_lua() { + let vm = RootVm::new(); + let top = vm.top(); + Lua::new().build().register(&vm).unwrap(); + Module::new(&[]).register(&vm).unwrap(); + //FIXME: Find a way to write the version differently. + vm.run_code::<()>( + c" + assert(bp3d.lua.name == 'bp3d-lua') + assert(bp3d.lua.version == '1.0.0-rc.2.1.0') + assert(#bp3d.lua.patches == 5) + local func = bp3d.lua.loadString('return 1 + 1') + assert(func) + assert(func() == 2) + local func, err = bp3d.lua.loadString('ret a + 2') + assert(func == nil) + assert(err == \"syntax error: [string \\\"ret a + 2\\\"]:1: '=' expected near 'a'\") + assert(bp3d.lua.runString('return 1 + 1') == 2) + ", + ) + .unwrap(); + let err = vm + .run_code::<()>(c"bp3d.lua.require \"not.existing.file\"") + .unwrap_err() + .into_runtime() + .unwrap(); + assert_eq!(err.msg(), "rust error: unknown source name not"); + vm.run_code::<()>( + c" + local function test() + bp3d.lua.require \"not.existing.file\" + end + local flag, err = bp3d.lua.pcall(test) + assert(not flag) + print(err) + assert(err ~= '') + ", + ) + .unwrap(); + let err = vm + .run_code::<()>(c"MODULES:load('broken', 'broken2')") + .unwrap_err() + .into_runtime() + .unwrap(); + assert_eq!( + err.msg(), + "rust error: module error: module not found (broken)" + ); + assert_eq!(vm.top(), top); +} + +#[test] +fn test_vm_lib_util() { + let mut vm = RootVm::new(); + let top = vm.top(); + Util.register(&mut vm).unwrap(); + vm.run_code::<()>( + c" + local src = { + a = 1, + b = 2 + } + local dst = { + c = 3 + } + bp3d.util.table.update(dst, src) + assert(dst.a == 1) + assert(dst.b == 2) + assert(dst.c == 3) + assert(bp3d.util.table.count(dst) == 3) + assert(bp3d.util.table.count(src) == 2) + assert(bp3d.util.table.contains(dst, 1)) + assert(bp3d.util.table.contains(dst, 2)) + assert(bp3d.util.table.contains(dst, 3)) + assert(bp3d.util.table.containsKey(dst, 'a')) + assert(bp3d.util.table.containsKey(dst, 'b')) + assert(bp3d.util.table.containsKey(dst, 'c')) + local str = bp3d.util.table.tostring(dst) .. '\\n' + assert(bp3d.util.string.contains(str, 'a: 1\\n')) + assert(bp3d.util.string.contains(str, 'b: 2\\n')) + assert(bp3d.util.string.contains(str, 'c: 3\\n')) + local str = bp3d.util.table.tostring(dst) + local tbl = bp3d.util.string.split(str, 0x0A) + assert(#tbl == 3) + assert(bp3d.util.table.contains(tbl, 'a: 1')) + assert(bp3d.util.table.contains(tbl, 'b: 2')) + assert(bp3d.util.table.contains(tbl, 'c: 3')) + ", + ) + .unwrap(); + vm.run_code::<()>( + c" + local utf8 = bp3d.util.utf8 + assert(utf8.fromString('abc') ~= nil) + assert(utf8.count('abc') == 3) + local tbl = utf8.split('a;b;c;d', ';') + assert(#tbl == 4) + assert(tbl[1] == 'a') + assert(tbl[2] == 'b') + assert(tbl[3] == 'c') + assert(tbl[4] == 'd') + assert(utf8.charAt('abc', 0) == 0x61) + assert(utf8.charAt('abc', 1) == 0x62) + assert(utf8.charAt('abc', 2) == 0x63) + local s = '我是' + assert(utf8.sub(s, 1) == '是') + ", + ) + .unwrap(); + vm.run_code::<()>( + c" + local tbl = { value = 42 } + local protected = bp3d.util.table.protect(tbl) + assert(protected.value == 42) + ", + ) + .unwrap(); + vm.run_code::<()>( + c" + local tbl = { value = 42 } + local protected = bp3d.util.table.protect(tbl) + protected.value = 84 + ", + ) + .unwrap_err(); + vm.run_code::<()>( + c" + local src = { value = 42, adding = { a = 1 } } + local dst = { value = 42, adding = { } } + bp3d.util.table.update(dst, src) + assert(dst.value == 42) + assert(dst.adding.a == 1) + local dst2 = { value = 42 } + bp3d.util.table.update(dst2, src) + assert(dst2.value == 42) + assert(dst2.adding.a == 1) + ", + ) + .unwrap(); + vm.run_code::<()>( + c" + local src = { value = 42, adding = { a = 1 } } + local dst = bp3d.util.table.copy(src) + assert(dst.value == 42) + assert(dst.adding.a == 1) + dst.adding.b = 2 + dst.b = 84 + assert(dst.b == 84) + assert(src.b == nil) + assert(dst.adding.b == 2) + assert(src.adding.b == nil) + ", + ) + .unwrap(); + vm.run_code::<()>( + c" + local list = { 1, 2, 3, 4 } + local list2 = { 5, 6, 7, 8 } + bp3d.util.table.concat(list, list2) + assert(#list == 8) + local str = bp3d.util.table.tostring(list) + assert(str == '1: 1\\n2: 2\\n3: 3\\n4: 4\\n5: 5\\n6: 6\\n7: 7\\n8: 8') + ", + ) + .unwrap(); + assert_eq!(vm.top(), top); +} + +#[test] +fn test_vm_lib_os_time() { + let mut vm = RootVm::new(); + bp3d_lua::libs::os::Time.register(&mut vm).unwrap(); + vm.run_code::<()>( + c" + time = bp3d.os.time.nowLocal() + time2 = bp3d.os.time.nowUtc() + ", + ) + .unwrap(); + std::thread::sleep(std::time::Duration::from_millis(500)); + vm.run_code::<()>( + c" + local function testDateTime(a, b) + local ymd = a:getDate() + local ymd2 = b:getDate() + assert(ymd.year == ymd2.year) + assert(ymd.month == ymd2.month) + assert(ymd.day == ymd2.day) + end + local now2 = bp3d.os.time.nowUtc() + local now = bp3d.os.time.nowLocal() + if (now ~= nil and time ~= nil) then + assert(now > time) + end + assert(now2 > time2) + if (now ~= nil and time ~= nil) then + testDateTime(now, time) + end + testDateTime(now2, time2) + ", + ) + .unwrap(); +} + +#[test] +fn test_vm_lib_os_instant() { + let mut vm = RootVm::new(); + bp3d_lua::libs::os::Instant.register(&mut vm).unwrap(); + vm.run_code::<()>( + c" + instant = bp3d.os.instant.now() + ", + ) + .unwrap(); + std::thread::sleep(std::time::Duration::from_millis(500)); + vm.run_code::<()>( + c" + local diff = instant:elapsed() + assert((diff - 0.5) < 0.2) + ", + ) + .unwrap(); +} + +#[test] +fn test_vm_lib_os() { + let mut vm = RootVm::new(); + bp3d_lua::libs::os::Compat.register(&mut vm).unwrap(); + vm.run_code::<()>( + c" + clock = os.clock() + ", + ) + .unwrap(); + std::thread::sleep(std::time::Duration::from_millis(500)); + vm.run_code::<()>( + c" + local now = os.clock() + assert((clock - now) < 0.1) + ", + ) + .unwrap(); + let s = vm + .run_code::<&str>( + c" + return os.date('!%H:%M:%S') + ", + ) + .unwrap(); + assert!(s.contains(":")); + assert!(!s.contains("[")); + assert!(!s.contains("]")); +} diff --git a/core/tests/test_vm_multivalue.rs b/core/tests/test_vm_multivalue.rs new file mode 100644 index 0000000..d217348 --- /dev/null +++ b/core/tests/test_vm_multivalue.rs @@ -0,0 +1,39 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::vm::RootVm; + +#[test] +fn test_vm_multivalue() { + let vm = RootVm::new(); + let (i, n): (i32, &str) = vm.run_code(c"return 123, 'this is a test'").unwrap(); + assert_eq!(i, 123); + assert_eq!(n, "this is a test"); +} diff --git a/core/tests/test_vm_run.rs b/core/tests/test_vm_run.rs new file mode 100644 index 0000000..20fb07e --- /dev/null +++ b/core/tests/test_vm_run.rs @@ -0,0 +1,100 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::ffi::lua::{State, ThreadStatus}; +use bp3d_lua::vm::core::load::{load_custom, Code, Script}; +use bp3d_lua::vm::core::util::ChunkNameBuilder; +use bp3d_lua::vm::core::Load; +use bp3d_lua::vm::{RootVm, Vm}; +use std::fmt::Write; + +struct BrokenReader; + +impl bp3d_lua::vm::core::load::Custom for BrokenReader { + type Error = bp3d_lua::vm::error::Error; + + fn read_data(&mut self) -> Result<&[u8], Self::Error> { + Err(bp3d_lua::vm::error::Error::Error) + } +} + +impl Load for BrokenReader { + fn load(self, l: State) -> ThreadStatus { + let mut builder = ChunkNameBuilder::new(); + let _ = write!(&mut builder, "broken"); + unsafe { load_custom(l, builder.build(), BrokenReader) } + } +} + +fn run_assert_err(vm: &Vm, obj: impl Load, err_msg: &str) { + let res = vm.run::<()>(obj); + assert!(res.is_err()); + let err = res.unwrap_err().into_runtime().unwrap(); + assert_eq!(err.msg(), err_msg); +} + +#[test] +fn test_vm_run() { + let vm = RootVm::new(); + let top = vm.top(); + run_assert_err( + &vm, + Code::new("test", b"return 1 + b"), + "test:1: attempt to perform arithmetic on global 'b' (a nil value)", + ); + run_assert_err( + &vm, + c"return 1 + b", + "[string \"return 1 + b\"]:1: attempt to perform arithmetic on global 'b' (a nil value)", + ); + run_assert_err(&vm, Code::new("this is an amazingly long text which should get truncated我", b"return 1 + b"), "this is an amazingly long text which should get truncated:1: attempt to perform arithmetic on global 'b' (a nil value)"); + let err = vm.run::<()>(BrokenReader).unwrap_err(); + assert_eq!( + err.to_string(), + "loader error: rust error: error in error handler" + ); + run_assert_err( + &vm, + Script::from_path("./tests/lua/basic.lua").unwrap(), + "basic.lua:2: nope", + ); + let err = vm + .run::<()>(Script::from_path("./tests/lua/broken.lua").unwrap()) + .unwrap_err(); + assert_eq!( + err.to_string(), + "syntax error: broken.lua:2: '(' expected near 'end'" + ); + vm.run::<()>(Script::from_path("./tests/lua/class.lua").unwrap()) + .unwrap(); + let func = vm.load_code(c"return 1 + b").unwrap(); + func.call::<()>(()).unwrap_err(); + assert_eq!(vm.top(), top + 1); +} diff --git a/core/tests/test_vm_run_string.rs b/core/tests/test_vm_run_string.rs new file mode 100644 index 0000000..b5d83b7 --- /dev/null +++ b/core/tests/test_vm_run_string.rs @@ -0,0 +1,58 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::decl_lib_func; +use bp3d_lua::vm::function::types::RFunction; +use bp3d_lua::vm::RootVm; + +decl_lib_func! { + fn dostring(vm: &Vm, code: &str) -> bp3d_lua::vm::Result<()> { + let ret = vm.run_code::<()>(code); + println!("Ran code: {}", code); + ret + } +} + +#[test] +fn test_run_string() { + let vm = RootVm::new(); + let res = vm.run_code::<()>(c"dostring('test')"); + assert!(res.is_err()); + assert_eq!(res.unwrap_err().to_string(), "runtime error: [string \"dostring('test')\"]:1: attempt to call global 'dostring' (a nil value)"); + vm.set_global(c"dostring", RFunction::wrap(dostring)) + .unwrap(); + assert!(vm.run_code::<()>(c"dostring('test')").is_err()); + assert!(vm + .run_code::<()>(c"dostring('print(\"whatever 123\")')") + .is_ok()); + assert!(vm.run_code::<()>(c"dostring('root = 42')").is_ok()); + let val: u32 = vm.get_global("root").unwrap(); + assert_eq!(val, 42); +} diff --git a/core/tests/test_vm_tables.rs b/core/tests/test_vm_tables.rs new file mode 100644 index 0000000..aa8d7cc --- /dev/null +++ b/core/tests/test_vm_tables.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::vm::table::Table; +use bp3d_lua::vm::RootVm; + +#[test] +fn tables() { + let mut vm = RootVm::new(); + let top = vm.top(); + vm.scope(|vm| { + let mut tbl = Table::new(vm); + tbl.set(c"a", 0.42)?; + tbl.set(c"b", "My great string")?; + let mut new_table = Table::new(vm); + new_table.set(c"whatever", 42)?; + let s: &str = tbl.get(c"b")?; + assert_eq!(s, "My great string"); + tbl.set(c"sub", new_table)?; + assert_eq!(tbl.len(), 3); + vm.set_global(c"myTable", tbl) + }) + .unwrap(); + let new_top = vm.top(); + assert_eq!(top, new_top); + let v = vm.run_code::(c"return myTable.c"); + assert!(v.is_err()); + let v = vm.run_code::(c"return myTable.a"); + assert!(v.is_ok()); + assert_eq!(v.unwrap(), 0.42); + let v = vm.run_code::<&str>(c"return myTable.b"); + assert!(v.is_ok()); + assert_eq!(v.unwrap(), "My great string"); + let v = vm.run_code::(c"return myTable.sub.whatever"); + assert!(v.is_ok()); + assert_eq!(v.unwrap(), 42); + vm.clear(); + let new_top_1 = vm.top(); + assert_eq!(new_top, new_top_1); + vm.scope(|vm| { + let tbl: Table = vm.get_global("myTable")?; + assert_eq!(tbl.len(), 3); + let v: f64 = tbl.get(c"a")?; + assert_eq!(v, 0.42); + let v = vm.run_code::<&str>(c"return myTable.b")?; + assert_eq!(v, "My great string"); + { + let v: f64 = tbl.get(c"a")?; + assert_eq!(v, 0.42); + } + assert_eq!(v, "My great string"); + Ok(()) + }) + .unwrap(); + assert_eq!(vm.top(), new_top); +} diff --git a/core/tests/test_vm_userdata.rs b/core/tests/test_vm_userdata.rs new file mode 100644 index 0000000..caeab53 --- /dev/null +++ b/core/tests/test_vm_userdata.rs @@ -0,0 +1,310 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "root-vm")] + +use bp3d_lua::ffi::lua::RawNumber; +use bp3d_lua::vm::function::types::RFunction; +use bp3d_lua::vm::userdata::LuaDrop; +use bp3d_lua::vm::{RootVm, Vm}; +use bp3d_lua::{decl_lib_func, decl_userdata, decl_userdata_mut}; +use std::sync::Mutex; + +static MUTEX: Mutex<()> = Mutex::new(()); + +static mut DROP_COUNTER: i32 = 0; +static mut LUA_DROP_COUNTER: i32 = 0; + +pub struct MyInt(i64); + +impl LuaDrop for MyInt { + fn lua_drop(&self, _: &Vm) { + unsafe { + LUA_DROP_COUNTER += 1; + } + } +} + +impl Drop for MyInt { + fn drop(&mut self) { + unsafe { + DROP_COUNTER += 1; + } + } +} + +decl_userdata! { + impl MyInt { + fn tonumber(this: &MyInt) -> RawNumber { + this.0 as _ + } + + fn tostring(this: &MyInt) -> String { + this.0.to_string() + } + + fn __eq(this: &MyInt, other: &MyInt) -> bool { + this.0 == other.0 + } + + fn __lt(this: &MyInt, other: &MyInt) -> bool { + this.0 < other.0 + } + + fn __gt(this: &MyInt, other: &MyInt) -> bool { + this.0 > other.0 + } + + fn __add(this: &MyInt, other: &MyInt) -> MyInt { + MyInt(this.0 + other.0) + } + } +} + +#[derive(Debug)] +pub struct BrokenObject; + +decl_userdata_mut! { + impl BrokenObject { + // this should blow up at init time + fn replace(this: &mut BrokenObject, other: &BrokenObject) -> () { + println!("this: {:?}, other: {:?}", this, other) + } + } +} + +pub struct BrokenObject2(pub u128); + +decl_userdata! { + impl BrokenObject2 { + } +} + +#[derive(Debug)] +pub struct BrokenObject3; + +decl_userdata! { + impl BrokenObject3 { + fn __gc(this: &BrokenObject3) -> () { + println!("{:?}", this); + } + } +} + +#[derive(Debug)] +pub struct BrokenObject4; + +decl_userdata! { + impl BrokenObject4 { + fn __index(this: &BrokenObject3) -> () { + println!("{:?}", this); + } + } +} + +decl_lib_func! { + fn my_int(i: i64) -> MyInt { + MyInt(i) + } +} + +#[test] +fn test_vm_userdata_forgot_reg() { + let vm = RootVm::new(); + vm.set_global(c"MyInt", RFunction::wrap(my_int)).unwrap(); + vm.run_code::<()>(c"a = MyInt(123)").unwrap(); + vm.run_code::<()>(c"b = MyInt(456)").unwrap(); + assert!(vm.run_code::(c"return a < b").is_err()); + assert!(vm.run_code::(c"return a + b").is_err()); +} + +#[test] +fn test_vm_userdata_error_handling() { + let vm = RootVm::new(); + let top = vm.top(); + vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake) + .unwrap(); + assert_eq!(top, vm.top()); + let res = vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake); + assert!(res.is_err()); + let msg = res.unwrap_err().to_string(); + assert_eq!( + msg, + "userdata: violation of the unique type rule for mutable method \"replace\"" + ); + assert_eq!(top, vm.top()); + let res = vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake); + assert!(res.is_err()); + let msg = res.unwrap_err().to_string(); + assert_eq!( + msg, + "userdata: too strict alignment required (16 bytes), max is 8 bytes" + ); + assert_eq!(top, vm.top()); + let res = vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake); + assert!(res.is_err()); + let msg = res.unwrap_err().to_string(); + assert_eq!(msg, "userdata: __gc meta-method is reserved for internal use, if you need Vm access in drop, please use LuaDrop"); + assert_eq!(top, vm.top()); + let res = vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake); + assert!(res.is_err()); + let msg = res.unwrap_err().to_string(); + assert_eq!( + msg, + "userdata: class name \"MyInt\" has already been registered" + ); + assert_eq!(top, vm.top()); + let res = vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake); + assert!(res.is_err()); + let msg = res.unwrap_err().to_string(); + assert_eq!(msg, "userdata: __index meta-method is required to be surrendered to luaL_newmetatable, it is impossible to bind custom code to __index"); + assert_eq!(top, vm.top()); +} + +fn test_vm_userdata_base(vm: &Vm) { + unsafe { + DROP_COUNTER = 0; + LUA_DROP_COUNTER = 0; + } + let top = vm.top(); + vm.register_userdata::(bp3d_lua::vm::userdata::case::Snake) + .unwrap(); + assert_eq!(top, vm.top()); + vm.set_global(c"MyInt", RFunction::wrap(my_int)).unwrap(); + assert_eq!(top, vm.top()); + vm.run_code::<()>(c"a = MyInt(123)").unwrap(); + vm.run_code::<()>(c"b = MyInt(456)").unwrap(); + vm.run_code::<()>(c"c = MyInt(456)").unwrap(); + assert_eq!(vm.run_code::(c"return a == b").unwrap(), false); + assert_eq!(vm.run_code::(c"return b == c").unwrap(), true); + assert_eq!(vm.run_code::(c"return a < b").unwrap(), true); + assert_eq!(vm.run_code::(c"return b > a").unwrap(), true); + assert_eq!(vm.run_code::<&MyInt>(c"return a + b").unwrap().0, 579); + assert_eq!( + vm.run_code::<&str>(c"return (a + b):tostring()").unwrap(), + "579" + ); + assert_eq!( + vm.run_code::(c"return (a + b):tonumber()") + .unwrap(), + 579.0 + ); + assert_eq!( + vm.run_code::(c"return a.tonumber(b)").unwrap(), + 456.0 + ); + assert_eq!(top + 8, vm.top()); +} + +#[test] +fn test_vm_userdata() { + let _guard = MUTEX.lock(); + { + let vm = RootVm::new(); + let top = vm.top(); + test_vm_userdata_base(&vm); + assert_eq!(top + 8, vm.top()); + } + assert_eq!(unsafe { DROP_COUNTER }, 6); + assert_eq!(unsafe { LUA_DROP_COUNTER }, 6); +} + +#[test] +fn test_vm_userdata_security1() { + let _guard = MUTEX.lock(); + { + let vm = RootVm::new(); + test_vm_userdata_base(&vm); + vm.run_code::<()>(c"getmetatable(a).__gc = function() print(\"Lua has hacked Rust\") end") + .unwrap_err(); + } + assert_eq!(unsafe { DROP_COUNTER }, 6); + assert_eq!(unsafe { LUA_DROP_COUNTER }, 6); +} + +#[test] +fn test_vm_userdata_security2() { + let _guard = MUTEX.lock(); + { + let vm = RootVm::new(); + test_vm_userdata_base(&vm); + vm.run_code::<()>(c"a.__gc = function() print(\"Lua has hacked Rust\") end") + .unwrap_err(); + } + assert_eq!(unsafe { DROP_COUNTER }, 6); + assert_eq!(unsafe { LUA_DROP_COUNTER }, 6); +} + +#[test] +fn test_vm_userdata_security3() { + let _guard = MUTEX.lock(); + { + let vm = RootVm::new(); + test_vm_userdata_base(&vm); + vm.run_code::<()>(c"setmetatable(a, nil)").unwrap_err(); + } + assert_eq!(unsafe { DROP_COUNTER }, 6); + assert_eq!(unsafe { LUA_DROP_COUNTER }, 6); +} + +#[test] +fn test_vm_userdata_security4() { + let _guard = MUTEX.lock(); + { + let vm = RootVm::new(); + test_vm_userdata_base(&vm); + vm.run_code::<()>( + c" + local func = a.tonumber + local tbl = {} + tbl.tonumber = func + tbl:tonumber() + ", + ) + .unwrap_err(); + } + assert_eq!(unsafe { DROP_COUNTER }, 6); + assert_eq!(unsafe { LUA_DROP_COUNTER }, 6); +} + +#[test] +fn test_vm_userdata_security5() { + let _guard = MUTEX.lock(); + { + let vm = RootVm::new(); + test_vm_userdata_base(&vm); + vm.run_code::<()>( + c" + rawset(a, '__gc', nil) + ", + ) + .unwrap_err(); + } + assert_eq!(unsafe { DROP_COUNTER }, 6); + assert_eq!(unsafe { LUA_DROP_COUNTER }, 6); +} diff --git a/patch/disable_lua_load.patch b/patch/disable_lua_load.patch new file mode 100644 index 0000000..73d1598 --- /dev/null +++ b/patch/disable_lua_load.patch @@ -0,0 +1,108 @@ +diff --git a/src/lib_base.c b/src/lib_base.c +index 5d1b88a9..f8419349 100644 +--- a/src/lib_base.c ++++ b/src/lib_base.c +@@ -361,103 +361,6 @@ LJLIB_ASM_(xpcall) LJLIB_REC(.) + + /* -- Base library: load Lua code ----------------------------------------- */ + +-static int load_aux(lua_State *L, int status, int envarg) +-{ +- if (status == LUA_OK) { +- /* +- ** Set environment table for top-level function. +- ** Don't do this for non-native bytecode, which returns a prototype. +- */ +- if (tvistab(L->base+envarg-1) && tvisfunc(L->top-1)) { +- GCfunc *fn = funcV(L->top-1); +- GCtab *t = tabV(L->base+envarg-1); +- setgcref(fn->c.env, obj2gco(t)); +- lj_gc_objbarrier(L, fn, t); +- } +- return 1; +- } else { +- setnilV(L->top-2); +- return 2; +- } +-} +- +-LJLIB_CF(loadfile) +-{ +- GCstr *fname = lj_lib_optstr(L, 1); +- GCstr *mode = lj_lib_optstr(L, 2); +- int status; +- lua_settop(L, 3); /* Ensure env arg exists. */ +- status = luaL_loadfilex(L, fname ? strdata(fname) : NULL, +- mode ? strdata(mode) : NULL); +- return load_aux(L, status, 3); +-} +- +-static const char *reader_func(lua_State *L, void *ud, size_t *size) +-{ +- UNUSED(ud); +- luaL_checkstack(L, 2, "too many nested functions"); +- copyTV(L, L->top++, L->base); +- lua_call(L, 0, 1); /* Call user-supplied function. */ +- L->top--; +- if (tvisnil(L->top)) { +- *size = 0; +- return NULL; +- } else if (tvisstr(L->top) || tvisnumber(L->top)) { +- copyTV(L, L->base+4, L->top); /* Anchor string in reserved stack slot. */ +- return lua_tolstring(L, 5, size); +- } else { +- lj_err_caller(L, LJ_ERR_RDRSTR); +- return NULL; +- } +-} +- +-LJLIB_CF(load) +-{ +- GCstr *name = lj_lib_optstr(L, 2); +- GCstr *mode = lj_lib_optstr(L, 3); +- int status; +- if (L->base < L->top && +- (tvisstr(L->base) || tvisnumber(L->base) || tvisbuf(L->base))) { +- const char *s; +- MSize len; +- if (tvisbuf(L->base)) { +- SBufExt *sbx = bufV(L->base); +- s = sbx->r; +- len = sbufxlen(sbx); +- if (!name) name = &G(L)->strempty; /* Buffers are not NUL-terminated. */ +- } else { +- GCstr *str = lj_lib_checkstr(L, 1); +- s = strdata(str); +- len = str->len; +- } +- lua_settop(L, 4); /* Ensure env arg exists. */ +- status = luaL_loadbufferx(L, s, len, name ? strdata(name) : s, +- mode ? strdata(mode) : NULL); +- } else { +- lj_lib_checkfunc(L, 1); +- lua_settop(L, 5); /* Reserve a slot for the string from the reader. */ +- status = lua_loadx(L, reader_func, NULL, name ? strdata(name) : "=(load)", +- mode ? strdata(mode) : NULL); +- } +- return load_aux(L, status, 4); +-} +- +-LJLIB_CF(loadstring) +-{ +- return lj_cf_load(L); +-} +- +-LJLIB_CF(dofile) +-{ +- GCstr *fname = lj_lib_optstr(L, 1); +- setnilV(L->top); +- L->top = L->base+1; +- if (luaL_loadfile(L, fname ? strdata(fname) : NULL) != LUA_OK) +- lua_error(L); +- lua_call(L, 0, LUA_MULTRET); +- return (int)(L->top - L->base) - 1; +-} +- + /* -- Base library: GC control -------------------------------------------- */ + + LJLIB_CF(gcinfo) diff --git a/patch/lib_init.patch b/patch/lib_init.patch new file mode 100644 index 0000000..3bed121 --- /dev/null +++ b/patch/lib_init.patch @@ -0,0 +1,43 @@ +diff --git a/src/lib_init.c b/src/lib_init.c +index 01cecf2f..8ed34a94 100644 +--- a/src/lib_init.c ++++ b/src/lib_init.c +@@ -17,25 +17,14 @@ + + static const luaL_Reg lj_lib_load[] = { + { "", luaopen_base }, +- { LUA_LOADLIBNAME, luaopen_package }, + { LUA_TABLIBNAME, luaopen_table }, +- { LUA_IOLIBNAME, luaopen_io }, +- { LUA_OSLIBNAME, luaopen_os }, + { LUA_STRLIBNAME, luaopen_string }, + { LUA_MATHLIBNAME, luaopen_math }, +- { LUA_DBLIBNAME, luaopen_debug }, + { LUA_BITLIBNAME, luaopen_bit }, + { LUA_JITLIBNAME, luaopen_jit }, + { NULL, NULL } + }; + +-static const luaL_Reg lj_lib_preload[] = { +-#if LJ_HASFFI +- { LUA_FFILIBNAME, luaopen_ffi }, +-#endif +- { NULL, NULL } +-}; +- + LUALIB_API void luaL_openlibs(lua_State *L) + { + const luaL_Reg *lib; +@@ -44,12 +33,5 @@ LUALIB_API void luaL_openlibs(lua_State *L) + lua_pushstring(L, lib->name); + lua_call(L, 1, 0); + } +- luaL_findtable(L, LUA_REGISTRYINDEX, "_PRELOAD", +- sizeof(lj_lib_preload)/sizeof(lj_lib_preload[0])-1); +- for (lib = lj_lib_preload; lib->func; lib++) { +- lua_pushcfunction(L, lib->func); +- lua_setfield(L, -2, lib->name); +- } +- lua_pop(L, 1); + } + diff --git a/patch/lj_disable_jit.patch b/patch/lj_disable_jit.patch new file mode 100644 index 0000000..27a4ef9 --- /dev/null +++ b/patch/lj_disable_jit.patch @@ -0,0 +1,659 @@ +diff --git a/src/lib_jit.c b/src/lib_jit.c +index fd8e585b..882f4425 100644 +--- a/src/lib_jit.c ++++ b/src/lib_jit.c +@@ -43,19 +43,15 @@ + static int setjitmode(lua_State *L, int mode) + { + int idx = 0; +- if (L->base == L->top || tvisnil(L->base)) { /* jit.on/off/flush([nil]) */ +- mode |= LUAJIT_MODE_ENGINE; +- } else { +- /* jit.on/off/flush(func|proto, nil|true|false) */ +- if (tvisfunc(L->base) || tvisproto(L->base)) +- idx = 1; +- else if (!tvistrue(L->base)) /* jit.on/off/flush(true, nil|true|false) */ +- goto err; +- if (L->base+1 < L->top && tvisbool(L->base+1)) +- mode |= boolV(L->base+1) ? LUAJIT_MODE_ALLFUNC : LUAJIT_MODE_ALLSUBFUNC; +- else +- mode |= LUAJIT_MODE_FUNC; +- } ++ /* jit.on/off/flush(func|proto, nil|true|false) */ ++ if (tvisfunc(L->base) || tvisproto(L->base)) ++ idx = 1; ++ else if (!tvistrue(L->base)) /* jit.on/off/flush(true, nil|true|false) */ ++ goto err; ++ if (L->base+1 < L->top && tvisbool(L->base+1)) ++ mode |= boolV(L->base+1) ? LUAJIT_MODE_ALLFUNC : LUAJIT_MODE_ALLSUBFUNC; ++ else ++ mode |= LUAJIT_MODE_FUNC; + if (luaJIT_setmode(L, idx, mode) != 1) { + if ((mode & LUAJIT_MODE_MASK) == LUAJIT_MODE_ENGINE) + lj_err_caller(L, LJ_ERR_NOJIT); +@@ -67,16 +63,25 @@ static int setjitmode(lua_State *L, int mode) + + LJLIB_CF(jit_on) + { ++ if (L->base == L->top || tvisnil(L->base)) { ++ luaL_error(L, "attempt to apply global jit state, this is forbidden by sandbox"); ++ } + return setjitmode(L, LUAJIT_MODE_ON); + } + + LJLIB_CF(jit_off) + { ++ if (L->base == L->top || tvisnil(L->base)) { ++ luaL_error(L, "attempt to apply global jit state, this is forbidden by sandbox"); ++ } + return setjitmode(L, LUAJIT_MODE_OFF); + } + + LJLIB_CF(jit_flush) + { ++ if (L->base == L->top || tvisnil(L->base)) { ++ luaL_error(L, "attempt to apply global jit state, this is forbidden by sandbox"); ++ } + #if LJ_HASJIT + if (L->base < L->top && tvisnumber(L->base)) { + int traceno = lj_lib_checkint(L, 1); +@@ -87,535 +92,29 @@ LJLIB_CF(jit_flush) + return setjitmode(L, LUAJIT_MODE_FLUSH); + } + +-#if LJ_HASJIT +-/* Push a string for every flag bit that is set. */ +-static void flagbits_to_strings(lua_State *L, uint32_t flags, uint32_t base, +- const char *str) +-{ +- for (; *str; base <<= 1, str += 1+*str) +- if (flags & base) +- setstrV(L, L->top++, lj_str_new(L, str+1, *(uint8_t *)str)); +-} +-#endif +- +-LJLIB_CF(jit_status) +-{ +-#if LJ_HASJIT +- jit_State *J = L2J(L); +- L->top = L->base; +- setboolV(L->top++, (J->flags & JIT_F_ON) ? 1 : 0); +- flagbits_to_strings(L, J->flags, JIT_F_CPU, JIT_F_CPUSTRING); +- flagbits_to_strings(L, J->flags, JIT_F_OPT, JIT_F_OPTSTRING); +- return (int)(L->top - L->base); +-#else +- setboolV(L->top++, 0); +- return 1; +-#endif +-} +- +-LJLIB_CF(jit_security) +-{ +- int idx = lj_lib_checkopt(L, 1, -1, LJ_SECURITY_MODESTRING); +- setintV(L->top++, ((LJ_SECURITY_MODE >> (2*idx)) & 3)); +- return 1; +-} +- +-LJLIB_CF(jit_attach) +-{ +-#ifdef LUAJIT_DISABLE_VMEVENT +- luaL_error(L, "vmevent API disabled"); +-#else +- GCfunc *fn = lj_lib_checkfunc(L, 1); +- GCstr *s = lj_lib_optstr(L, 2); +- luaL_findtable(L, LUA_REGISTRYINDEX, LJ_VMEVENTS_REGKEY, LJ_VMEVENTS_HSIZE); +- if (s) { /* Attach to given event. */ +- const uint8_t *p = (const uint8_t *)strdata(s); +- uint32_t h = s->len; +- while (*p) h = h ^ (lj_rol(h, 6) + *p++); +- lua_pushvalue(L, 1); +- lua_rawseti(L, -2, VMEVENT_HASHIDX(h)); +- G(L)->vmevmask = VMEVENT_NOCACHE; /* Invalidate cache. */ +- } else { /* Detach if no event given. */ +- setnilV(L->top++); +- while (lua_next(L, -2)) { +- L->top--; +- if (tvisfunc(L->top) && funcV(L->top) == fn) { +- setnilV(lj_tab_set(L, tabV(L->top-2), L->top-1)); +- } +- } +- } +-#endif +- return 0; +-} +- +-LJLIB_PUSH(top-5) LJLIB_SET(os) +-LJLIB_PUSH(top-4) LJLIB_SET(arch) +-LJLIB_PUSH(top-3) LJLIB_SET(version_num) ++LJLIB_PUSH(top-4) LJLIB_SET(os) ++LJLIB_PUSH(top-3) LJLIB_SET(arch) + LJLIB_PUSH(top-2) LJLIB_SET(version) + + #include "lj_libdef.h" + +-/* -- jit.util.* functions ------------------------------------------------ */ +- +-#define LJLIB_MODULE_jit_util +- +-/* -- Reflection API for Lua functions ------------------------------------ */ +- +-static void setintfield(lua_State *L, GCtab *t, const char *name, int32_t val) +-{ +- setintV(lj_tab_setstr(L, t, lj_str_newz(L, name)), val); +-} +- +-/* local info = jit.util.funcinfo(func [,pc]) */ +-LJLIB_CF(jit_util_funcinfo) +-{ +- GCproto *pt = lj_lib_checkLproto(L, 1, 1); +- if (pt) { +- BCPos pc = (BCPos)lj_lib_optint(L, 2, 0); +- GCtab *t; +- lua_createtable(L, 0, 16); /* Increment hash size if fields are added. */ +- t = tabV(L->top-1); +- setintfield(L, t, "linedefined", pt->firstline); +- setintfield(L, t, "lastlinedefined", pt->firstline + pt->numline); +- setintfield(L, t, "stackslots", pt->framesize); +- setintfield(L, t, "params", pt->numparams); +- setintfield(L, t, "bytecodes", (int32_t)pt->sizebc); +- setintfield(L, t, "gcconsts", (int32_t)pt->sizekgc); +- setintfield(L, t, "nconsts", (int32_t)pt->sizekn); +- setintfield(L, t, "upvalues", (int32_t)pt->sizeuv); +- if (pc < pt->sizebc) +- setintfield(L, t, "currentline", lj_debug_line(pt, pc)); +- lua_pushboolean(L, (pt->flags & PROTO_VARARG)); +- lua_setfield(L, -2, "isvararg"); +- lua_pushboolean(L, (pt->flags & PROTO_CHILD)); +- lua_setfield(L, -2, "children"); +- setstrV(L, L->top++, proto_chunkname(pt)); +- lua_setfield(L, -2, "source"); +- lj_debug_pushloc(L, pt, pc); +- lua_setfield(L, -2, "loc"); +- setprotoV(L, lj_tab_setstr(L, t, lj_str_newlit(L, "proto")), pt); +- } else { +- GCfunc *fn = funcV(L->base); +- GCtab *t; +- lua_createtable(L, 0, 4); /* Increment hash size if fields are added. */ +- t = tabV(L->top-1); +- if (!iscfunc(fn)) +- setintfield(L, t, "ffid", fn->c.ffid); +- setintptrV(lj_tab_setstr(L, t, lj_str_newlit(L, "addr")), +- (intptr_t)(void *)fn->c.f); +- setintfield(L, t, "upvalues", fn->c.nupvalues); +- } +- return 1; +-} ++/* -- JIT compiler configuration ----------------------------------------- */ + +-/* local ins, m = jit.util.funcbc(func, pc) */ +-LJLIB_CF(jit_util_funcbc) +-{ +- GCproto *pt = lj_lib_checkLproto(L, 1, 0); +- BCPos pc = (BCPos)lj_lib_checkint(L, 2); +- if (pc < pt->sizebc) { +- BCIns ins = proto_bc(pt)[pc]; +- BCOp op = bc_op(ins); +- lj_assertL(op < BC__MAX, "bad bytecode op %d", op); +- setintV(L->top, ins); +- setintV(L->top+1, lj_bc_mode[op]); +- L->top += 2; +- return 2; +- } +- return 0; ++LUA_API uint32_t lua_ext_getjitflags(lua_State *L) { ++ return L2J(L)->flags; + } + +-/* local k = jit.util.funck(func, idx) */ +-LJLIB_CF(jit_util_funck) +-{ +- GCproto *pt = lj_lib_checkLproto(L, 1, 0); +- ptrdiff_t idx = (ptrdiff_t)lj_lib_checkint(L, 2); +- if (idx >= 0) { +- if (idx < (ptrdiff_t)pt->sizekn) { +- copyTV(L, L->top-1, proto_knumtv(pt, idx)); +- return 1; ++LUA_API int lua_ext_setjitflags(lua_State *L, uint32_t flags) { ++ jit_State *J = L2J(L); ++ uint32_t current = J->flags; ++ if ((current & JIT_F_ON) != (flags & JIT_F_ON)) { ++ // we have attempted to change the global JIT mode outside of the setjitmode ext function, error. ++ return -1; + } +- } else { +- if (~idx < (ptrdiff_t)pt->sizekgc) { +- GCobj *gc = proto_kgc(pt, idx); +- setgcV(L, L->top-1, gc, ~gc->gch.gct); +- return 1; +- } +- } +- return 0; +-} +- +-/* local name = jit.util.funcuvname(func, idx) */ +-LJLIB_CF(jit_util_funcuvname) +-{ +- GCproto *pt = lj_lib_checkLproto(L, 1, 0); +- uint32_t idx = (uint32_t)lj_lib_checkint(L, 2); +- if (idx < pt->sizeuv) { +- setstrV(L, L->top-1, lj_str_newz(L, lj_debug_uvname(pt, idx))); +- return 1; +- } +- return 0; +-} +- +-/* -- Reflection API for traces ------------------------------------------- */ +- +-#if LJ_HASJIT +- +-/* Check trace argument. Must not throw for non-existent trace numbers. */ +-static GCtrace *jit_checktrace(lua_State *L) +-{ +- TraceNo tr = (TraceNo)lj_lib_checkint(L, 1); +- jit_State *J = L2J(L); +- if (tr > 0 && tr < J->sizetrace) +- return traceref(J, tr); +- return NULL; +-} +- +-/* Names of link types. ORDER LJ_TRLINK */ +-static const char *const jit_trlinkname[] = { +- "none", "root", "loop", "tail-recursion", "up-recursion", "down-recursion", +- "interpreter", "return", "stitch" +-}; +- +-/* local info = jit.util.traceinfo(tr) */ +-LJLIB_CF(jit_util_traceinfo) +-{ +- GCtrace *T = jit_checktrace(L); +- if (T) { +- GCtab *t; +- lua_createtable(L, 0, 8); /* Increment hash size if fields are added. */ +- t = tabV(L->top-1); +- setintfield(L, t, "nins", (int32_t)T->nins - REF_BIAS - 1); +- setintfield(L, t, "nk", REF_BIAS - (int32_t)T->nk); +- setintfield(L, t, "link", T->link); +- setintfield(L, t, "nexit", T->nsnap); +- setstrV(L, L->top++, lj_str_newz(L, jit_trlinkname[T->linktype])); +- lua_setfield(L, -2, "linktype"); +- /* There are many more fields. Add them only when needed. */ +- return 1; +- } +- return 0; +-} +- +-/* local m, ot, op1, op2, prev = jit.util.traceir(tr, idx) */ +-LJLIB_CF(jit_util_traceir) +-{ +- GCtrace *T = jit_checktrace(L); +- IRRef ref = (IRRef)lj_lib_checkint(L, 2) + REF_BIAS; +- if (T && ref >= REF_BIAS && ref < T->nins) { +- IRIns *ir = &T->ir[ref]; +- int32_t m = lj_ir_mode[ir->o]; +- setintV(L->top-2, m); +- setintV(L->top-1, ir->ot); +- setintV(L->top++, (int32_t)ir->op1 - (irm_op1(m)==IRMref ? REF_BIAS : 0)); +- setintV(L->top++, (int32_t)ir->op2 - (irm_op2(m)==IRMref ? REF_BIAS : 0)); +- setintV(L->top++, ir->prev); +- return 5; +- } +- return 0; +-} +- +-/* local k, t [, slot] = jit.util.tracek(tr, idx) */ +-LJLIB_CF(jit_util_tracek) +-{ +- GCtrace *T = jit_checktrace(L); +- IRRef ref = (IRRef)lj_lib_checkint(L, 2) + REF_BIAS; +- if (T && ref >= T->nk && ref < REF_BIAS) { +- IRIns *ir = &T->ir[ref]; +- int32_t slot = -1; +- if (ir->o == IR_KSLOT) { +- slot = ir->op2; +- ir = &T->ir[ir->op1]; +- } +-#if LJ_HASFFI +- if (ir->o == IR_KINT64) ctype_loadffi(L); +-#endif +- lj_ir_kvalue(L, L->top-2, ir); +- setintV(L->top-1, (int32_t)irt_type(ir->t)); +- if (slot == -1) +- return 2; +- setintV(L->top++, slot); +- return 3; +- } +- return 0; +-} +- +-/* local snap = jit.util.tracesnap(tr, sn) */ +-LJLIB_CF(jit_util_tracesnap) +-{ +- GCtrace *T = jit_checktrace(L); +- SnapNo sn = (SnapNo)lj_lib_checkint(L, 2); +- if (T && sn < T->nsnap) { +- SnapShot *snap = &T->snap[sn]; +- SnapEntry *map = &T->snapmap[snap->mapofs]; +- MSize n, nent = snap->nent; +- GCtab *t; +- lua_createtable(L, nent+2, 0); +- t = tabV(L->top-1); +- setintV(lj_tab_setint(L, t, 0), (int32_t)snap->ref - REF_BIAS); +- setintV(lj_tab_setint(L, t, 1), (int32_t)snap->nslots); +- for (n = 0; n < nent; n++) +- setintV(lj_tab_setint(L, t, (int32_t)(n+2)), (int32_t)map[n]); +- setintV(lj_tab_setint(L, t, (int32_t)(nent+2)), (int32_t)SNAP(255, 0, 0)); +- return 1; +- } +- return 0; +-} +- +-/* local mcode, addr, loop = jit.util.tracemc(tr) */ +-LJLIB_CF(jit_util_tracemc) +-{ +- GCtrace *T = jit_checktrace(L); +- if (T && T->mcode != NULL) { +- setstrV(L, L->top-1, lj_str_new(L, (const char *)T->mcode, T->szmcode)); +- setintptrV(L->top++, (intptr_t)(void *)T->mcode); +- setintV(L->top++, T->mcloop); +- return 3; +- } +- return 0; +-} +- +-/* local addr = jit.util.traceexitstub([tr,] exitno) */ +-LJLIB_CF(jit_util_traceexitstub) +-{ +-#ifdef EXITSTUBS_PER_GROUP +- ExitNo exitno = (ExitNo)lj_lib_checkint(L, 1); +- jit_State *J = L2J(L); +- if (exitno < EXITSTUBS_PER_GROUP*LJ_MAX_EXITSTUBGR) { +- setintptrV(L->top-1, (intptr_t)(void *)exitstub_addr(J, exitno)); +- return 1; +- } +-#else +- if (L->top > L->base+1) { /* Don't throw for one-argument variant. */ +- GCtrace *T = jit_checktrace(L); +- ExitNo exitno = (ExitNo)lj_lib_checkint(L, 2); +- ExitNo maxexit = T->root ? T->nsnap+1 : T->nsnap; +- if (T && T->mcode != NULL && exitno < maxexit) { +- setintptrV(L->top-1, (intptr_t)(void *)exitstub_trace_addr(T, exitno)); +- return 1; +- } +- } +-#endif +- return 0; +-} +- +-/* local addr = jit.util.ircalladdr(idx) */ +-LJLIB_CF(jit_util_ircalladdr) +-{ +- uint32_t idx = (uint32_t)lj_lib_checkint(L, 1); +- if (idx < IRCALL__MAX) { +- ASMFunction func = lj_ir_callinfo[idx].func; +- setintptrV(L->top-1, (intptr_t)(void *)lj_ptr_strip(func)); +- return 1; +- } +- return 0; +-} +- +-#endif +- +-#include "lj_libdef.h" +- +-static int luaopen_jit_util(lua_State *L) +-{ +- LJ_LIB_REG(L, NULL, jit_util); +- return 1; +-} +- +-/* -- jit.opt module ------------------------------------------------------ */ +- +-#if LJ_HASJIT +- +-#define LJLIB_MODULE_jit_opt +- +-/* Parse optimization level. */ +-static int jitopt_level(jit_State *J, const char *str) +-{ +- if (str[0] >= '0' && str[0] <= '9' && str[1] == '\0') { +- uint32_t flags; +- if (str[0] == '0') flags = JIT_F_OPT_0; +- else if (str[0] == '1') flags = JIT_F_OPT_1; +- else if (str[0] == '2') flags = JIT_F_OPT_2; +- else flags = JIT_F_OPT_3; +- J->flags = (J->flags & ~JIT_F_OPT_MASK) | flags; +- return 1; /* Ok. */ +- } +- return 0; /* No match. */ +-} +- +-/* Parse optimization flag. */ +-static int jitopt_flag(jit_State *J, const char *str) +-{ +- const char *lst = JIT_F_OPTSTRING; +- uint32_t opt; +- int set = 1; +- if (str[0] == '+') { +- str++; +- } else if (str[0] == '-') { +- str++; +- set = 0; +- } else if (str[0] == 'n' && str[1] == 'o') { +- str += str[2] == '-' ? 3 : 2; +- set = 0; +- } +- for (opt = JIT_F_OPT; ; opt <<= 1) { +- size_t len = *(const uint8_t *)lst; +- if (len == 0) +- break; +- if (strncmp(str, lst+1, len) == 0 && str[len] == '\0') { +- if (set) J->flags |= opt; else J->flags &= ~opt; +- return 1; /* Ok. */ +- } +- lst += 1+len; +- } +- return 0; /* No match. */ +-} +- +-/* Parse optimization parameter. */ +-static int jitopt_param(jit_State *J, const char *str) +-{ +- const char *lst = JIT_P_STRING; +- int i; +- for (i = 0; i < JIT_P__MAX; i++) { +- size_t len = *(const uint8_t *)lst; +- lj_assertJ(len != 0, "bad JIT_P_STRING"); +- if (strncmp(str, lst+1, len) == 0 && str[len] == '=') { +- int32_t n = 0; +- const char *p = &str[len+1]; +- while (*p >= '0' && *p <= '9') +- n = n*10 + (*p++ - '0'); +- if (*p) return 0; /* Malformed number. */ +- J->param[i] = n; +- if (i == JIT_P_hotloop) +- lj_dispatch_init_hotcount(J2G(J)); +- return 1; /* Ok. */ +- } +- lst += 1+len; +- } +- return 0; /* No match. */ +-} +- +-/* jit.opt.start(flags...) */ +-LJLIB_CF(jit_opt_start) +-{ +- jit_State *J = L2J(L); +- int nargs = (int)(L->top - L->base); +- if (nargs == 0) { +- J->flags = (J->flags & ~JIT_F_OPT_MASK) | JIT_F_OPT_DEFAULT; +- } else { +- int i; +- for (i = 1; i <= nargs; i++) { +- const char *str = strdata(lj_lib_checkstr(L, i)); +- if (!jitopt_level(J, str) && +- !jitopt_flag(J, str) && +- !jitopt_param(J, str)) +- lj_err_callerv(L, LJ_ERR_JITOPT, str); +- } +- } +- return 0; +-} +- +-#include "lj_libdef.h" +- +-#endif +- +-/* -- jit.profile module -------------------------------------------------- */ +- +-#if LJ_HASPROFILE +- +-#define LJLIB_MODULE_jit_profile +- +-/* Not loaded by default, use: local profile = require("jit.profile") */ +- +-#define KEY_PROFILE_THREAD (U64x(80000000,00000000)|'t') +-#define KEY_PROFILE_FUNC (U64x(80000000,00000000)|'f') +- +-static void jit_profile_callback(lua_State *L2, lua_State *L, int samples, +- int vmstate) +-{ +- TValue key; +- cTValue *tv; +- key.u64 = KEY_PROFILE_FUNC; +- tv = lj_tab_get(L, tabV(registry(L)), &key); +- if (tvisfunc(tv)) { +- char vmst = (char)vmstate; +- int status; +- setfuncV(L2, L2->top++, funcV(tv)); +- setthreadV(L2, L2->top++, L); +- setintV(L2->top++, samples); +- setstrV(L2, L2->top++, lj_str_new(L2, &vmst, 1)); +- status = lua_pcall(L2, 3, 0, 0); /* callback(thread, samples, vmstate) */ +- if (status) { +- if (G(L2)->panic) G(L2)->panic(L2); +- exit(EXIT_FAILURE); +- } +- lj_trace_abort(G(L2)); +- } +-} +- +-/* profile.start(mode, cb) */ +-LJLIB_CF(jit_profile_start) +-{ +- GCtab *registry = tabV(registry(L)); +- GCstr *mode = lj_lib_optstr(L, 1); +- GCfunc *func = lj_lib_checkfunc(L, 2); +- lua_State *L2 = lua_newthread(L); /* Thread that runs profiler callback. */ +- TValue key; +- /* Anchor thread and function in registry. */ +- key.u64 = KEY_PROFILE_THREAD; +- setthreadV(L, lj_tab_set(L, registry, &key), L2); +- key.u64 = KEY_PROFILE_FUNC; +- setfuncV(L, lj_tab_set(L, registry, &key), func); +- lj_gc_anybarriert(L, registry); +- luaJIT_profile_start(L, mode ? strdata(mode) : "", +- (luaJIT_profile_callback)jit_profile_callback, L2); +- return 0; +-} +- +-/* profile.stop() */ +-LJLIB_CF(jit_profile_stop) +-{ +- GCtab *registry; +- TValue key; +- luaJIT_profile_stop(L); +- registry = tabV(registry(L)); +- key.u64 = KEY_PROFILE_THREAD; +- setnilV(lj_tab_set(L, registry, &key)); +- key.u64 = KEY_PROFILE_FUNC; +- setnilV(lj_tab_set(L, registry, &key)); +- lj_gc_anybarriert(L, registry); +- return 0; +-} +- +-/* dump = profile.dumpstack([thread,] fmt, depth) */ +-LJLIB_CF(jit_profile_dumpstack) +-{ +- lua_State *L2 = L; +- int arg = 0; +- size_t len; +- int depth; +- GCstr *fmt; +- const char *p; +- if (L->top > L->base && tvisthread(L->base)) { +- L2 = threadV(L->base); +- arg = 1; +- } +- fmt = lj_lib_checkstr(L, arg+1); +- depth = lj_lib_checkint(L, arg+2); +- p = luaJIT_profile_dumpstack(L2, strdata(fmt), depth, &len); +- lua_pushlstring(L, p, len); +- return 1; +-} +- +-#include "lj_libdef.h" +- +-static int luaopen_jit_profile(lua_State *L) +-{ +- LJ_LIB_REG(L, NULL, jit_profile); +- return 1; ++ J->flags = flags; ++ return 0; + } + +-#endif +- + /* -- JIT compiler initialization ----------------------------------------- */ + + #if LJ_HASJIT +@@ -725,20 +224,9 @@ LUALIB_API int luaopen_jit(lua_State *L) + #endif + lua_pushliteral(L, LJ_OS_NAME); + lua_pushliteral(L, LJ_ARCH_NAME); +- lua_pushinteger(L, LUAJIT_VERSION_NUM); /* Deprecated. */ + lua_pushliteral(L, LUAJIT_VERSION); + LJ_LIB_REG(L, LUA_JITLIBNAME, jit); +-#if LJ_HASPROFILE +- lj_lib_prereg(L, LUA_JITLIBNAME ".profile", luaopen_jit_profile, +- tabref(L->env)); +-#endif +-#ifndef LUAJIT_DISABLE_JITUTIL +- lj_lib_prereg(L, LUA_JITLIBNAME ".util", luaopen_jit_util, tabref(L->env)); +-#endif +-#if LJ_HASJIT +- LJ_LIB_REG(L, "jit.opt", jit_opt); +-#endif +- L->top -= 2; ++ L->top -= 1; + return 1; + } + +diff --git a/src/lj_dispatch.c b/src/lj_dispatch.c +index 78608316..94f0077e 100644 +--- a/src/lj_dispatch.c ++++ b/src/lj_dispatch.c +@@ -241,6 +241,24 @@ static void setptmode_all(global_State *g, GCproto *pt, int mode) + } + #endif + ++LUA_API int lua_ext_setjitmode(lua_State *L, int mode) { ++ global_State *g = G(L); ++ lj_trace_abort(g); /* Abort recording on any state change. */ ++ /* Avoid pulling the rug from under our own feet. */ ++ if ((g->hookmask & HOOK_GC)) ++ return -1; ++ if ((mode & LUAJIT_MODE_FLUSH)) { ++ lj_trace_flushall(L); ++ } else { ++ if (!(mode & LUAJIT_MODE_ON)) ++ G2J(g)->flags &= ~(uint32_t)JIT_F_ON; ++ else ++ G2J(g)->flags |= (uint32_t)JIT_F_ON; ++ lj_dispatch_update(g); ++ } ++ return 0; ++} ++ + /* Public API function: control the JIT engine. */ + int luaJIT_setmode(lua_State *L, int idx, int mode) + { diff --git a/patch/lua_ext.patch b/patch/lua_ext.patch new file mode 100644 index 0000000..5cfa5bc --- /dev/null +++ b/patch/lua_ext.patch @@ -0,0 +1,49 @@ +diff --git a/src/lj_api.c b/src/lj_api.c +index e9fc25b4..7e21f135 100644 +--- a/src/lj_api.c ++++ b/src/lj_api.c +@@ -1317,3 +1317,44 @@ LUA_API void lua_setallocf(lua_State *L, lua_Alloc f, void *ud) + g->allocf = f; + } + ++/*------------*/ ++/* Extensions */ ++/*------------*/ ++ ++LUALIB_API int lua_ext_tab_len(lua_State* L, int idx, MSize* outsize) ++{ ++ TValue* o = index2adr(L, idx); ++ GCtab* tab = tabV(o); ++ if (LJ_LIKELY(tab->hmask == 0)) ++ return lj_tab_len(tab); ++ // Return an error as the table has a hash part. ++ return -1; ++} ++ ++LUALIB_API lua_Number lua_ext_fast_checknumber(lua_State *L, int idx) ++{ ++ cTValue *o = index2adr(L, idx); ++ if (LJ_LIKELY(tvisnumber(o))) ++ return numberVnum(o); ++ else ++ lj_err_argt(L, idx, LUA_TNUMBER); ++ return 0; ++} ++ ++LUALIB_API lua_Integer lua_ext_fast_checkinteger(lua_State *L, int idx) ++{ ++ cTValue *o = index2adr(L, idx); ++ lua_Number n; ++ if (LJ_LIKELY(tvisint(o))) { ++ return intV(o); ++ } else if (LJ_LIKELY(tvisnum(o))) { ++ n = numV(o); ++ } else { ++ lj_err_argt(L, idx, LUA_TNUMBER); ++ } ++#if LJ_64 ++ return (lua_Integer)n; ++#else ++ return lj_num2int(n); ++#endif ++} diff --git a/patch/lua_ext_ccatch_error.patch b/patch/lua_ext_ccatch_error.patch new file mode 100644 index 0000000..b053521 --- /dev/null +++ b/patch/lua_ext_ccatch_error.patch @@ -0,0 +1,118 @@ +diff --git a/src/lj_err.c b/src/lj_err.c +index 03b5030b..8a1377e6 100644 +--- a/src/lj_err.c ++++ b/src/lj_err.c +@@ -175,7 +175,7 @@ static void *err_unwind(lua_State *L, void *stopcf, int errcode) + case FRAME_PCALLH: /* FF pcall() frame inside hook. */ + if (errcode) { + global_State *g; +- if (errcode == LUA_YIELD) { ++ if (errcode == LUA_YIELD || errcode == LUA_ERRCCATCH) { + frame = frame_prevd(frame); + break; + } +@@ -825,7 +825,7 @@ LJ_NOINLINE void lj_err_mem(lua_State *L) + } + + /* Find error function for runtime errors. Requires an extra stack traversal. */ +-static ptrdiff_t finderrfunc(lua_State *L) ++static ptrdiff_t finderrfunc(lua_State *L, int errcode) + { + cTValue *frame = L->base-1, *bot = tvref(L->stack)+LJ_FR2; + void *cf = L->cframe; +@@ -864,6 +864,10 @@ static ptrdiff_t finderrfunc(lua_State *L) + break; + case FRAME_PCALL: + case FRAME_PCALLH: ++ if (errcode == LUA_ERRCCATCH) { ++ frame = frame_prevd(frame); ++ break; ++ } + if (frame_func(frame_prevd(frame))->c.ffid == FF_xpcall) + return savestack(L, frame_prevd(frame)+1); /* xpcall's errorfunc. */ + return 0; +@@ -878,7 +882,7 @@ static ptrdiff_t finderrfunc(lua_State *L) + /* Runtime error. */ + LJ_NOINLINE void LJ_FASTCALL lj_err_run(lua_State *L) + { +- ptrdiff_t ef = (LJ_HASJIT && tvref(G(L)->jit_base)) ? 0 : finderrfunc(L); ++ ptrdiff_t ef = (LJ_HASJIT && tvref(G(L)->jit_base)) ? 0 : finderrfunc(L, LUA_ERRRUN); + if (ef) { + TValue *errfunc, *top; + lj_state_checkstack(L, LUA_MINSTACK * 2); /* Might raise new error. */ +@@ -899,6 +903,29 @@ LJ_NOINLINE void LJ_FASTCALL lj_err_run(lua_State *L) + lj_err_throw(L, LUA_ERRRUN); + } + ++LJ_NOINLINE void LJ_FASTCALL lj_err_ccatch(lua_State *L) ++{ ++ ptrdiff_t ef = (LJ_HASJIT && tvref(G(L)->jit_base)) ? 0 : finderrfunc(L, LUA_ERRCCATCH); ++ if (ef) { ++ TValue *errfunc, *top; ++ lj_state_checkstack(L, LUA_MINSTACK * 2); /* Might raise new error. */ ++ lj_trace_abort(G(L)); ++ errfunc = restorestack(L, ef); ++ top = L->top; ++ if (!tvisfunc(errfunc) || L->status == LUA_ERRERR) { ++ setstrV(L, top-1, lj_err_str(L, LJ_ERR_ERRERR)); ++ lj_err_throw(L, LUA_ERRERR); ++ } ++ L->status = LUA_ERRERR; ++ copyTV(L, top+LJ_FR2, top-1); ++ copyTV(L, top-1, errfunc); ++ if (LJ_FR2) setnilV(top++); ++ L->top = top+1; ++ lj_vm_call(L, top, 1+1); /* Stack: |errfunc|msg| -> |msg| */ ++ } ++ lj_err_throw(L, LUA_ERRCCATCH); ++} ++ + /* Stack overflow error. */ + void LJ_FASTCALL lj_err_stkov(lua_State *L) + { +@@ -912,6 +939,8 @@ LJ_NOINLINE void LJ_FASTCALL lj_err_trace(lua_State *L, int errcode) + { + if (errcode == LUA_ERRRUN) + lj_err_run(L); ++ else if (errcode == LUA_ERRCCATCH) ++ lj_err_ccatch(L); + else + lj_err_throw(L, errcode); + } +@@ -1122,6 +1151,12 @@ LUA_API int lua_error(lua_State *L) + return 0; /* unreachable */ + } + ++LUA_API int lua_ext_ccatch_error(lua_State *L) ++{ ++ lj_err_ccatch(L); ++ return 0; /* unreachable */ ++} ++ + LUALIB_API int luaL_argerror(lua_State *L, int narg, const char *msg) + { + err_argmsg(L, narg, msg); +diff --git a/src/lj_err.h b/src/lj_err.h +index 0cb945b0..37b80a42 100644 +--- a/src/lj_err.h ++++ b/src/lj_err.h +@@ -25,6 +25,7 @@ LJ_FUNCA_NORET void LJ_FASTCALL lj_err_throw(lua_State *L, int errcode); + LJ_FUNC_NORET void lj_err_mem(lua_State *L); + LJ_FUNC_NORET void LJ_FASTCALL lj_err_stkov(lua_State *L); + LJ_FUNC_NORET void LJ_FASTCALL lj_err_run(lua_State *L); ++LJ_FUNC_NORET void LJ_FASTCALL lj_err_ccatch(lua_State *L); + #if LJ_HASJIT + LJ_FUNCA_NORET void LJ_FASTCALL lj_err_trace(lua_State *L, int errcode); + #endif +diff --git a/src/lua.h b/src/lua.h +index 6d1634d1..0eb0917b 100644 +--- a/src/lua.h ++++ b/src/lua.h +@@ -46,6 +46,7 @@ + #define LUA_ERRSYNTAX 3 + #define LUA_ERRMEM 4 + #define LUA_ERRERR 5 ++#define LUA_ERRCCATCH 6 + + + typedef struct lua_State lua_State; diff --git a/patch/lua_load_no_bc.patch b/patch/lua_load_no_bc.patch new file mode 100644 index 0000000..635638b --- /dev/null +++ b/patch/lua_load_no_bc.patch @@ -0,0 +1,77 @@ +diff --git a/src/lj_lex.c b/src/lj_lex.c +index a986aeb8..a8427739 100644 +--- a/src/lj_lex.c ++++ b/src/lj_lex.c +@@ -397,7 +397,6 @@ static LexToken lex_scan(LexState *ls, TValue *tv) + /* Setup lexer state. */ + int lj_lex_setup(lua_State *L, LexState *ls) + { +- int header = 0; + ls->L = L; + ls->fs = NULL; + ls->pe = ls->p = NULL; +@@ -413,33 +412,6 @@ int lj_lex_setup(lua_State *L, LexState *ls) + ls->endmark = 0; + ls->fr2 = LJ_FR2; /* Generate native bytecode by default. */ + lex_next(ls); /* Read-ahead first char. */ +- if (ls->c == 0xef && ls->p + 2 <= ls->pe && (uint8_t)ls->p[0] == 0xbb && +- (uint8_t)ls->p[1] == 0xbf) { /* Skip UTF-8 BOM (if buffered). */ +- ls->p += 2; +- lex_next(ls); +- header = 1; +- } +- if (ls->c == '#') { /* Skip POSIX #! header line. */ +- do { +- lex_next(ls); +- if (ls->c == LEX_EOF) return 0; +- } while (!lex_iseol(ls)); +- lex_newline(ls); +- header = 1; +- } +- if (ls->c == LUA_SIGNATURE[0]) { /* Bytecode dump. */ +- if (header) { +- /* +- ** Loading bytecode with an extra header is disabled for security +- ** reasons. This may circumvent the usual check for bytecode vs. +- ** Lua code by looking at the first char. Since this is a potential +- ** security violation no attempt is made to echo the chunkname either. +- */ +- setstrV(L, L->top++, lj_err_str(L, LJ_ERR_BCBAD)); +- lj_err_throw(L, LUA_ERRSYNTAX); +- } +- return 1; +- } + return 0; + } + +diff --git a/src/lj_load.c b/src/lj_load.c +index 828bf8ae..7b7f1478 100644 +--- a/src/lj_load.c ++++ b/src/lj_load.c +@@ -30,24 +30,10 @@ static TValue *cpparser(lua_State *L, lua_CFunction dummy, void *ud) + LexState *ls = (LexState *)ud; + GCproto *pt; + GCfunc *fn; +- int bc; + UNUSED(dummy); + cframe_errfunc(L->cframe) = -1; /* Inherit error function. */ +- bc = lj_lex_setup(L, ls); +- if (ls->mode) { +- int xmode = 1; +- const char *mode = ls->mode; +- char c; +- while ((c = *mode++)) { +- if (c == (bc ? 'b' : 't')) xmode = 0; +- if (c == (LJ_FR2 ? 'W' : 'X')) ls->fr2 = !LJ_FR2; +- } +- if (xmode) { +- setstrV(L, L->top++, lj_err_str(L, LJ_ERR_XMODE)); +- lj_err_throw(L, LUA_ERRSYNTAX); +- } +- } +- pt = bc ? lj_bcread(ls) : lj_parse(ls); ++ (void)lj_lex_setup(L, ls); ++ pt = lj_parse(ls); + if (ls->fr2 == LJ_FR2) { + fn = lj_func_newL_empty(L, pt, tabref(L->env)); + /* Don't combine above/below into one statement. */ diff --git a/patch/windows_set_lib_names.patch b/patch/windows_set_lib_names.patch new file mode 100644 index 0000000..38b48df --- /dev/null +++ b/patch/windows_set_lib_names.patch @@ -0,0 +1,13 @@ +diff --git a/src/msvcbuild.bat b/src/msvcbuild.bat +index 69c0c61a..0665eaa3 100644 +--- a/src/msvcbuild.bat ++++ b/src/msvcbuild.bat +@@ -29,8 +29,6 @@ + @set DASMDIR=..\dynasm + @set DASM=%DASMDIR%\dynasm.lua + @set DASC=vm_x64.dasc +-@set LJDLLNAME=lua51.dll +-@set LJLIBNAME=lua51.lib + @set ALL_LIB=lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c + + @setlocal diff --git a/testbin/.cargo/config b/testbin/.cargo/config new file mode 100644 index 0000000..446973d --- /dev/null +++ b/testbin/.cargo/config @@ -0,0 +1,7 @@ +[target.x86_64-unknown-linux-gnu.lua] +rustc-link-search = [] +rustc-link-lib = [] + +[target.aarch64-apple-darwin.lua] +rustc-link-search = [] +rustc-link-lib = [] diff --git a/testbin/Cargo.toml b/testbin/Cargo.toml new file mode 100644 index 0000000..b93341a --- /dev/null +++ b/testbin/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "testbin" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +mlua = { version = "0.10.3", features = ["luajit"] } +bp3d-lua = { version = "1.0.0-rc.2.0.0", path = "../core", features = ["root-vm"] } +bp3d-os = { version = "1.0.0-rc.4.2.1", features = ["time"] } + +[workspace] +members = [] diff --git a/testbin/src/context.rs b/testbin/src/context.rs new file mode 100644 index 0000000..52a50ef --- /dev/null +++ b/testbin/src/context.rs @@ -0,0 +1,117 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use bp3d_lua::decl_closure; +use bp3d_lua::vm::closure::context::{CellMut, ContextMut}; +use bp3d_lua::vm::RootVm; +use mlua::{Lua, UserDataMethods}; +use std::time::Duration; + +struct TestContext { + value3: Vec, +} + +decl_closure! { + fn context_push |ctx: ContextMut| (val: u64) -> () { + let mut ctx = ctx; + ctx.value3.push(val); + } +} + +decl_closure! { + fn context_pop |ctx: ContextMut| () -> Option { + let mut ctx = ctx; + ctx.value3.pop() + } +} + +pub fn test_context_mlua() -> Duration { + let lua = Lua::new(); + lua.register_userdata_type::(|reg| { + reg.add_method_mut("push", |_, this, val: u64| { + this.value3.push(val); + Ok(()) + }); + reg.add_method_mut("pop", |_, this, _: ()| Ok(this.value3.pop())); + }) + .unwrap(); + let mut ctx = TestContext { value3: Vec::new() }; + let time = bp3d_os::time::Instant::now(); + for _ in 0..20000 { + lua.scope(|l| { + let ud = l.create_any_userdata_ref_mut(&mut ctx).unwrap(); + lua.globals().set("ctx", ud).unwrap(); + lua.load("assert(ctx:pop() == nil)").eval::<()>().unwrap(); + lua.load("ctx:push(1)").eval::<()>().unwrap(); + lua.load("ctx:push(2)").eval::<()>().unwrap(); + lua.load("ctx:push(3)").eval::<()>().unwrap(); + Ok(()) + }) + .unwrap(); + lua.scope(|l| { + let ud = l.create_any_userdata_ref_mut(&mut ctx).unwrap(); + lua.globals().set("ctx", ud).unwrap(); + lua.load("assert(ctx:pop() == 3)").eval::<()>().unwrap(); + lua.load("assert(ctx:pop() == 2)").eval::<()>().unwrap(); + lua.load("assert(ctx:pop() == 1)").eval::<()>().unwrap(); + lua.load("assert(ctx:pop() == nil)").eval::<()>().unwrap(); + lua.load("assert(ctx:pop() == nil)").eval::<()>().unwrap(); + Ok(()) + }) + .unwrap(); + } + time.elapsed() +} + +pub fn test_context_vm() -> Duration { + let vm = RootVm::new(); + let ctx = ContextMut::new(&vm); + vm.set_global(c"context_push", context_push(ctx)).unwrap(); + vm.set_global(c"context_pop", context_pop(ctx)).unwrap(); + let mut obj = TestContext { value3: vec![] }; + let mut ctx = CellMut::new(ctx); + let time = bp3d_os::time::Instant::now(); + for _ in 0..20000 { + { + let _obj = ctx.bind(&mut obj); + vm.run_code::<()>(c"assert(context_pop() == nil)").unwrap(); + vm.run_code::<()>(c"context_push(1)").unwrap(); + vm.run_code::<()>(c"context_push(2)").unwrap(); + vm.run_code::<()>(c"context_push(3)").unwrap(); + } + { + let _obj = ctx.bind(&mut obj); + vm.run_code::<()>(c"assert(context_pop() == 3)").unwrap(); + vm.run_code::<()>(c"assert(context_pop() == 2)").unwrap(); + vm.run_code::<()>(c"assert(context_pop() == 1)").unwrap(); + vm.run_code::<()>(c"assert(context_pop() == nil)").unwrap(); + vm.run_code::<()>(c"assert(context_pop() == nil)").unwrap(); + } + } + time.elapsed() +} diff --git a/testbin/src/context_opt.rs b/testbin/src/context_opt.rs new file mode 100644 index 0000000..e465335 --- /dev/null +++ b/testbin/src/context_opt.rs @@ -0,0 +1,143 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use bp3d_lua::decl_closure; +use bp3d_lua::vm::closure::context::{CellMut, ContextMut}; +use bp3d_lua::vm::value::types::Function as LuaFunction; +use bp3d_lua::vm::RootVm; +use mlua::{Function, Lua, UserDataMethods}; +use std::time::Duration; + +struct TestContext { + value3: Vec, +} + +decl_closure! { + fn context_push |ctx: ContextMut| (val: u64) -> () { + let mut ctx = ctx; + ctx.value3.push(val); + } +} + +decl_closure! { + fn context_pop |ctx: ContextMut| () -> Option { + let mut ctx = ctx; + ctx.value3.pop() + } +} + +pub fn test_context_mlua() -> Duration { + let lua = Lua::new(); + lua.register_userdata_type::(|reg| { + reg.add_method_mut("push", |_, this, val: u64| { + this.value3.push(val); + Ok(()) + }); + reg.add_method_mut("pop", |_, this, _: ()| Ok(this.value3.pop())); + }) + .unwrap(); + lua.load( + " + function part1(ctx) + assert(ctx:pop() == nil) + ctx:push(1) + ctx:push(2) + ctx:push(3) + end + function part2(ctx) + assert(ctx:pop() == 3) + assert(ctx:pop() == 2) + assert(ctx:pop() == 1) + assert(ctx:pop() == nil) + assert(ctx:pop() == nil) + end + ", + ) + .eval::<()>() + .unwrap(); + let part1: Function = lua.globals().get("part1").unwrap(); + let part2: Function = lua.globals().get("part2").unwrap(); + let mut ctx = TestContext { value3: Vec::new() }; + let time = bp3d_os::time::Instant::now(); + for _ in 0..20000 { + lua.scope(|l| { + let ud = l.create_any_userdata_ref_mut(&mut ctx).unwrap(); + part1.call::<()>(ud).unwrap(); + Ok(()) + }) + .unwrap(); + lua.scope(|l| { + let ud = l.create_any_userdata_ref_mut(&mut ctx).unwrap(); + part2.call::<()>(ud).unwrap(); + Ok(()) + }) + .unwrap(); + } + time.elapsed() +} + +pub fn test_context_vm() -> Duration { + let vm = RootVm::new(); + let ctx = ContextMut::new(&vm); + vm.set_global(c"context_push", context_push(ctx)).unwrap(); + vm.set_global(c"context_pop", context_pop(ctx)).unwrap(); + vm.run_code::<()>( + c" + function part1() + assert(context_pop() == nil) + context_push(1) + context_push(2) + context_push(3) + end + function part2() + assert(context_pop() == 3) + assert(context_pop() == 2) + assert(context_pop() == 1) + assert(context_pop() == nil) + assert(context_pop() == nil) + end + ", + ) + .unwrap(); + let part1: LuaFunction = vm.get_global("part1").unwrap(); + let part2: LuaFunction = vm.get_global("part2").unwrap(); + let mut obj = TestContext { value3: vec![] }; + let mut ctx = CellMut::new(ctx); + let time = bp3d_os::time::Instant::now(); + for _ in 0..20000 { + { + let _obj = ctx.bind(&mut obj); + part1.call::<()>(()).unwrap(); + } + { + let _obj = ctx.bind(&mut obj); + part2.call::<()>(()).unwrap(); + } + } + time.elapsed() +} diff --git a/testbin/src/main.rs b/testbin/src/main.rs new file mode 100644 index 0000000..9268866 --- /dev/null +++ b/testbin/src/main.rs @@ -0,0 +1,145 @@ +// Copyright (c) 2025, BlockProject 3D +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of BlockProject 3D nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod context; +mod context_opt; + +use bp3d_lua::decl_lib_func; +use bp3d_lua::vm::function::types::RFunction; +use bp3d_lua::vm::RootVm; +use mlua::Lua; +use std::time::Duration; + +struct ValueWithDrop; +impl ValueWithDrop { + pub fn print(&self) {} +} +impl Drop for ValueWithDrop { + fn drop(&mut self) {} +} + +decl_lib_func! { + fn test_c_function(name: &str, value: f64) -> String { + let drop = ValueWithDrop; + drop.print(); + format!("Hello {} ({})", name, value) + } +} + +fn test_vm_destructor() -> Duration { + let mut vm = RootVm::new(); + vm.set_global(c"test_c_function", RFunction::wrap(test_c_function)) + .unwrap(); + let time = bp3d_os::time::Instant::now(); + for _ in 0..20000 { + let res = vm.run_code::<&str>(c"return test_c_function('this is a test\\xFF', 0.42)"); + assert!(res.is_err()); + assert!(vm + .run_code::<&str>(c"return test_c_function('this is a test', 0.42)") + .is_ok()); + let s = vm + .run_code::<&str>(c"return test_c_function('this is a test', 0.42)") + .unwrap(); + assert_eq!(s, "Hello this is a test (0.42)"); + vm.clear(); + } + time.elapsed() +} + +fn test_vm_mlua() -> Duration { + let lua = Lua::new(); + let f = lua + .create_function(|_, (name, value): (String, f64)| { + let drop = ValueWithDrop; + drop.print(); + Ok(format!("Hello {} ({})", name, value)) + }) + .unwrap(); + lua.globals().set("test_c_function", f).unwrap(); + let time = bp3d_os::time::Instant::now(); + for _ in 0..20000 { + let res: mlua::Result = lua + .load("return test_c_function('this is a test\\xFF', 0.42)") + .call(()); + assert!(res.is_err()); + assert!(lua + .load("return test_c_function('this is a test', 0.42)") + .call::(()) + .is_ok()); + let s: String = lua + .load("return test_c_function('this is a test', 0.42)") + .call(()) + .unwrap(); + assert_eq!(s, "Hello this is a test (0.42)"); + } + time.elapsed() +} + +fn main() { + const RUNS: u32 = 10; + let mut lua = Duration::new(0, 0); + let mut mlua = Duration::new(0, 0); + let mut ctx_lua = Duration::new(0, 0); + let mut ctx_mlua = Duration::new(0, 0); + let mut ctx_lua_opt = Duration::new(0, 0); + let mut ctx_mlua_opt = Duration::new(0, 0); + + for _ in 0..RUNS { + lua += test_vm_destructor(); + mlua += test_vm_mlua(); + ctx_lua += context::test_context_vm(); + ctx_mlua += context::test_context_mlua(); + ctx_lua_opt += context_opt::test_context_vm(); + ctx_mlua_opt += context_opt::test_context_mlua(); + } + + lua /= RUNS; + mlua /= RUNS; + ctx_lua /= RUNS; + ctx_mlua /= RUNS; + ctx_lua_opt /= RUNS; + ctx_mlua_opt /= RUNS; + + println!("average tools.lua (basic): {:?}", lua); + println!("average mlua (basic): {:?}", mlua); + assert!(lua < mlua); + println!("average diff (basic): {:?}", mlua - lua); + + println!("average tools.lua (context): {:?}", ctx_lua); + println!("average mlua (context): {:?}", ctx_mlua); + assert!(ctx_lua < ctx_mlua); + println!("average diff (context): {:?}", ctx_mlua - ctx_lua); + + println!("average tools.lua (context_opt): {:?}", ctx_lua_opt); + println!("average mlua (context_opt): {:?}", ctx_mlua_opt); + assert!(ctx_lua_opt < ctx_mlua_opt); + println!( + "average diff (context_opt): {:?}", + ctx_mlua_opt - ctx_lua_opt + ); +}