Repository: HKalbasi/zngur Branch: main Commit: cce4441a58de Files: 213 Total size: 508.1 KB Directory structure: gitextract_ukr27c6s/ ├── .cargo/ │ └── config.toml ├── .github/ │ └── workflows/ │ ├── ci.yml │ ├── publish.yml │ └── site.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benchmark/ │ ├── .gitignore │ ├── Cargo.toml │ ├── benches/ │ │ └── simple.rs │ ├── build.rs │ ├── impls.cpp │ ├── main.zng │ └── src/ │ └── lib.rs ├── cspell-words.txt ├── cspell.json ├── dprint.json ├── examples/ │ ├── .gitignore │ ├── char/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── conditional/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── cxx_demo/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── blobstore.cpp │ │ ├── build.rs │ │ ├── expected_output.txt │ │ ├── main.zng │ │ └── src/ │ │ └── main.rs │ ├── cxx_demo_split/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── blobstore.cpp │ │ ├── build.rs │ │ ├── expected_output.txt │ │ ├── main.zng │ │ └── src/ │ │ └── main.rs │ ├── impl_trait/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── memory_management/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── multiple_modules/ │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── aggregation/ │ │ │ ├── Cargo.toml │ │ │ ├── aggregation.zng │ │ │ ├── impls.cpp │ │ │ ├── src/ │ │ │ │ └── lib.rs │ │ │ └── stats.h │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── packet/ │ │ │ ├── Cargo.toml │ │ │ ├── packet.zng │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── processor/ │ │ │ ├── Cargo.toml │ │ │ ├── processor.zng │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── receiver/ │ │ │ ├── Cargo.toml │ │ │ ├── receiver.zng │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── src/ │ │ └── lib.rs │ ├── raw_pointer/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── rayon/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── rayon_split/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── regression_test1/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── rustyline/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── simple/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── simple_import/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── bar.cpp │ │ ├── bar.zng │ │ ├── expected_output.txt │ │ ├── foo.cpp │ │ ├── foo.zng │ │ ├── main.cpp │ │ ├── main.zng │ │ ├── primitives.zng │ │ └── src/ │ │ └── lib.rs │ ├── tutorial/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main.zng │ │ └── src/ │ │ └── lib.rs │ ├── tutorial-wasm32/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── NMakefile │ │ ├── README.md │ │ ├── expected_output.txt │ │ ├── main.cpp │ │ ├── main32.zng │ │ └── src/ │ │ └── lib.rs │ └── tutorial_cpp/ │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── expected_output.txt │ ├── impls.cpp │ ├── inventory.h │ ├── main.zng │ └── src/ │ └── main.rs ├── mise.toml ├── xtask/ │ ├── Cargo.toml │ └── src/ │ ├── ci.rs │ ├── format_book.rs │ ├── format_templates.rs │ └── main.rs ├── zngur/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── zngur-autozng/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── zngur-cli/ │ ├── Cargo.toml │ └── src/ │ ├── cfg_extractor.rs │ └── main.rs ├── zngur-def/ │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ └── merge.rs ├── zngur-generator/ │ ├── Cargo.toml │ ├── src/ │ │ ├── cpp.rs │ │ ├── lib.rs │ │ ├── rust.rs │ │ └── template.rs │ └── templates/ │ ├── cpp_header.sptl │ ├── cpp_source.sptl │ └── zng_header.sptl └── zngur-parser/ ├── Cargo.toml └── src/ ├── cfg.rs ├── conditional.rs ├── lib.rs └── tests.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] xtask = "run --package xtask --" ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: ["main"] pull_request: branches: ["main"] env: CARGO_TERM_COLOR: always jobs: build-mac: runs-on: macos-14 strategy: fail-fast: false steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v3 - name: Install Rust stable run: | rustup toolchain install stable rustup target add wasm32-wasip1 rustup component add rustfmt - name: Install Clang 19 run: brew install llvm@19 - name: Run `cargo xtask ci` run: CC=$(brew --prefix llvm@19)/bin/clang CXX=$(brew --prefix llvm@19)/bin/clang++ cargo xtask ci build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: cpp_compiler: - clang++ - g++ env: CXX: ${{ matrix.cpp_compiler }} steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v3 - name: Install Rust stable run: | rustup toolchain install stable rustup target add wasm32-wasip1 rustup component add rustfmt - name: Install LLD for clang++ if: matrix.cpp_compiler == 'clang++' run: sudo apt-get update && sudo apt-get install -y lld - name: Run CI (clang++) if: matrix.cpp_compiler == 'clang++' run: CC=clang cargo xtask ci - name: Run CI (g++) if: matrix.cpp_compiler == 'g++' run: cargo xtask ci build-nightly: runs-on: ubuntu-latest strategy: fail-fast: false env: CXX: g++ steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v3 - name: Install Rust nightly run: | rustup toolchain install nightly rustup default nightly rustup target add wasm32-wasip1 rustup component add rustfmt - name: Run CI (g++ nightly) run: cargo xtask ci build-win: runs-on: windows-2022 steps: - name: Turn off autocrlf run: | git config --global core.autocrlf false - uses: actions/checkout@v3 - name: Enter VS Developer shell uses: ilammy/msvc-dev-cmd@v1 with: arch: amd64 vsversion: 2022 - uses: jdx/mise-action@v3 - name: Install Rust stable run: | rustup toolchain install stable rustup target add wasm32-wasip1 rustup component add rustfmt - name: Run `cargo xtask ci` run: cargo xtask ci ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: workflow_dispatch: jobs: publish: name: Publish runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: token: ${{ secrets.PAT }} fetch-depth: 0 - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - name: Install cargo-workspaces uses: actions-rs/install@v0.1 with: crate: cargo-workspaces version: 0.2.44 - name: Release env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} shell: bash run: | # Check if we can skip releasing a new version # (there are no changes and the job was not manually triggered) export CHANGED=$(cargo workspaces changed --include-merged-tags --ignore-changes "**/Cargo.toml") if [[ -z "$CHANGED" && "$GITHUB_EVENT_NAME" != "workflow_dispatch" ]]; then # Nothing has changed, so don't publish a new version echo "No changes detected, skipping publish." exit 0 fi # Update version git config --global user.email "runner@gha.local" git config --global user.name "Github Action" cargo workspaces -v version -ay --force '*' --include-merged-tags --no-git-commit --exact minor export VERSION=$(cd zngur; cargo pkgid | sed -E 's/.*#(.*)/\1/g') # Commit and publish git commit -am "Release $VERSION" git tag "v$VERSION" cargo workspaces -v publish --from-git --skip-published git push --tags git push ================================================ FILE: .github/workflows/site.yml ================================================ name: Deploy on: push: branches: - main paths: - book/** - .github/workflows/site.yml workflow_dispatch: jobs: deploy: name: Deploy runs-on: ubuntu-latest permissions: contents: write timeout-minutes: 30 steps: - uses: actions/checkout@v3 - uses: dtolnay/install@mdbook - run: mdbook --version - name: Build run: | cd book mdbook build - uses: crazy-max/ghaction-github-pages@v3.1.0 with: build_dir: book/book env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ /target a.out a.out.dSYM *.exe *.obj *.bat *.a *.o .vscode compile_commands.json zngur-autozng/doc.json perf.data perf.data.old actual_output.txt flamegraph.svg generated*.* ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ "zngur", "zngur-cli", "zngur-def", "zngur-generator", "zngur-parser", "zngur-autozng", "examples/*", "xtask", "benchmark", ] # Exclude tutorial-wasm32 from default workspace builds since it requires extra dependencies exclude = [ "examples/tutorial-wasm32", ] resolver = "2" [workspace.package] edition = "2024" rust-version = "1.85" license = "MIT OR Apache-2.0" [profile.dev] opt-level = 3 [profile.release] lto = true ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: LICENSE-MIT ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Zngur [github](https://github.com/hkalbasi/zngur) [crates.io](https://crates.io/crates/zngur) [docs.rs](https://docs.rs/zngur) [build status](https://github.com/hkalbasi/zngur/actions?query=branch%3Amain) Zngur (/zængɑr/) is a C++/Rust interop tool. It tries to expose arbitrary Rust types, methods and functions, while preserving its semantics and ergonomics as much as possible. Using Zngur, you can use arbitrary Rust crates in your C++ code as easily as using it in normal Rust code, and you can write idiomatic Rusty APIs for your C++ library inside C++. See [the documentation](https://hkalbasi.github.io/zngur/) for more info. ## Demo ```C++ #include #include #include "./generated.h" // Rust values are available in the `::rust` namespace from their absolute path // in Rust template using Vec = rust::std::vec::Vec; template using Option = rust::std::option::Option; template using BoxDyn = rust::Box>; // You can implement Rust traits for your classes template class VectorIterator : public rust::std::iter::Iterator { std::vector vec; size_t pos; public: VectorIterator(std::vector &&v) : vec(v), pos(0) {} ~VectorIterator() { std::cout << "vector iterator has been destructed" << std::endl; } Option next() override { if (pos >= vec.size()) { return Option::None(); } T value = vec[pos++]; // You can construct Rust enum with fields in C++ return Option::Some(value); } }; int main() { // You can call Rust functions that return things by value, and store that // value in your stack. auto s = Vec::new_(); s.push(2); Vec::push(s, 5); s.push(7); Vec::push(s, 3); // You can call Rust functions just like normal Rust. std::cout << s.clone().into_iter().sum() << std::endl; // You can catch Rust panics as C++ exceptions try { std::cout << "s[2] = " << *s.get(2).unwrap() << std::endl; std::cout << "s[4] = " << *s.get(4).unwrap() << std::endl; } catch (rust::Panic e) { std::cout << "Rust panic happened" << std::endl; } int state = 0; // You can convert a C++ lambda into a `Box` and friends. auto f = BoxDyn>::make_box([&](int32_t x) { state += x; std::cout << "hello " << x << " " << state << "\n"; return x * 2; }); // And pass it to Rust functions that accept closures. auto x = s.into_iter().map(std::move(f)).sum(); std::cout << x << " " << state << "\n"; std::vector vec{10, 20, 60}; // You can convert a C++ type that implements `Trait` to a `Box`. // `make_box` is similar to the `make_unique`, it takes constructor arguments // and construct it inside the `Box` (instead of `unique_ptr`). auto vec_as_iter = BoxDyn>::make_box< VectorIterator>(std::move(vec)); // Then use it like a normal Rust value. auto t = vec_as_iter.collect(); // Some utilities are also provided. For example, `zngur_dbg` is the // equivalent of `dbg!` macro. zngur_dbg(t); } ``` Output: ``` 17 s[2] = 7 thread '' panicked at 'called `Option::unwrap()` on a `None` value', examples/simple/src/generated.rs:186:39 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace s[4] = Rust panic happened hello 2 2 hello 5 7 hello 7 14 hello 3 17 34 17 vector iterator has been destructed [main.cpp:71] t = [ 10, 20, 60, ] ``` See the [`examples/simple`](https://github.com/HKalbasi/zngur/blob/main/examples/simple) if you want to build and run it. ## Installation The `zngur` CLI tool can be installed with cargo: ``` cargo install zngur-cli ```
## Contributing Because our examples require invoking a built `zngur` binary, we use `xtask` to orchestrate the build process and validate correctness across all examples. 1. **Install mise**: This project uses `mise` for tool management. Install it from [mise.jdx.dev](https://mise.jdx.dev/) or via your package manager. 2. **Install dependencies**: Run `mise install` to install the required tools. See [`mise.toml`](/mise.toml) for the list of installed tools. 3. **Run the CI locally**: The official command to run our CI is: ```bash CXX=clang++ cargo xtask ci ``` You can use any of the C++ compilers mentioned in our CI workflow: `clang++` or `g++`. Other compilers may work, but are not guaranteed to by our CI testing. ### Formatting You may run `cargo xtask format-book` to run `mdformat` on the `/book` directory. #### License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ================================================ FILE: benchmark/.gitignore ================================================ /target ================================================ FILE: benchmark/Cargo.toml ================================================ [package] name = "benchmark" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [dependencies] [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } [build-dependencies] cc = "1.0" build-rs = "0.1.2" zngur = { path = "../zngur" } [[bench]] name = "simple" harness = false ================================================ FILE: benchmark/benches/simple.rs ================================================ use criterion::{Criterion, criterion_group, criterion_main}; use std::hint::black_box; fn criterion_benchmark(c: &mut Criterion) { c.bench_function("build_vec_by_push 10_000 rust", |b| { b.iter(|| benchmark::build_vec_by_push_rust(black_box(10_000))) }); c.bench_function("build_vec_by_push 10_000 cpp", |b| { b.iter(|| benchmark::build_vec_by_push_cpp(black_box(10_000))) }); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: benchmark/build.rs ================================================ use zngur::Zngur; fn main() { build::rerun_if_changed("main.zng"); build::rerun_if_changed("impls.cpp"); #[cfg(not(target_os = "windows"))] let cxx = std::env::var("CXX").unwrap_or("c++".to_owned()); #[cfg(not(target_os = "windows"))] let lto_enabled = cxx.ends_with("clang++") && cfg!(target_os = "linux"); #[cfg(not(target_os = "windows"))] if lto_enabled { build::rustc_env("RUSTC_FLAGS", "-C linker-plugin-lto -C linker=clang"); build::rustc_link_arg("-fuse-ld=lld"); } let crate_dir = build::cargo_manifest_dir(); let out_dir = build::out_dir(); Zngur::from_zng_file(crate_dir.join("main.zng")) .with_cpp_file(out_dir.join("generated.cpp")) .with_h_file(out_dir.join("generated.h")) .with_rs_file(out_dir.join("generated.rs")) .with_zng_header_in_place_as(true) .generate(); let my_build = &mut cc::Build::new(); let my_build = my_build.cpp(true).std("c++17"); #[cfg(not(target_os = "windows"))] my_build.compiler(cxx); my_build.include(&crate_dir).include(&out_dir); #[cfg(not(target_os = "windows"))] if lto_enabled { my_build.flag("-flto"); } let my_build = || my_build.clone(); my_build() .file(out_dir.join("generated.cpp")) .compile("zngur_generated"); my_build().file("impls.cpp").compile("impls"); } ================================================ FILE: benchmark/impls.cpp ================================================ #include template using Vec = rust::std::vec::Vec; using u64 = uint64_t; namespace rust::exported_functions { auto build_vec_by_push_cpp(u64 n) -> Vec { auto v = Vec::new_(); for (u64 i = 0; i < n; ++i) { v.push(i); } return v; } } ================================================ FILE: benchmark/main.zng ================================================ type ::std::vec::Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> ::std::vec::Vec; fn push(&mut self, u64); } extern "C++" { fn build_vec_by_push_cpp(u64) -> ::std::vec::Vec; } ================================================ FILE: benchmark/src/lib.rs ================================================ mod generated { include!(concat!(env!("OUT_DIR"), "/generated.rs")); } pub fn build_vec_by_push_rust(n: u64) -> Vec { let mut v = Vec::new(); for i in 0..n { v.push(i); } v } pub fn build_vec_by_push_cpp(n: u64) -> Vec { generated::build_vec_by_push_cpp(n) } ================================================ FILE: cspell-words.txt ================================================ alignas alignof assocs autozng barbaz barbazxxx blobstore cerr chumsky cloneable Conds constexpr cout cplusplus CPTE Crubit csignal cstddef cstdint cstring CXXFLAGS decltype delem deinit depfile digset discontiguous emsdk endl fastlink flto foldl foldr funcs fvec GNUC hkalbasi ilog impls indexmap inplace itertools libexample LIBPATH libyourcrate lwasi mdformat memcpy moveit msvc newtype noexcept nmake ntdll pmut primitve println ptrdiff refmut relpath repr respecify rlib rustc RUSTC RUSTFLAGS RUSTLIB Rustup rustup rustyline Rustyline scrutinee scrutinees serde SIGSEGV sptl sref ssize staticlib strvec stmnt unimportability Uninit uninit usize userenv WASI WINLIBS wasi WASIFLAGS wasip wasmtime xshell xtask zngur Zngur Zngur's zngur's zængɑr zigza zoop ifndef endif ifdef selectany declspec elif askama endfor ================================================ FILE: cspell.json ================================================ { "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", "version": "0.2", "dictionaryDefinitions": [ { "name": "project-words", "path": "./cspell-words.txt", "addWords": true } ], "dictionaries": [ "project-words" ], "ignorePaths": [ "target", "*generated*.*", "/cspell-words.txt", "*.zng.h", "*.svg", "examples/**/Makefile", "examples/**/NMakefile", "actual_output.txt" ] } ================================================ FILE: dprint.json ================================================ { "markdown": { "lineWidth": 100, "emphasisKind": "asterisks", "strongKind": "asterisks", "textWrap": "maintain", "ignoreDirective": "dprint-ignore", "ignoreFileDirective": "dprint-ignore-file", "ignoreStartDirective": "dprint-ignore-start", "ignoreEndDirective": "dprint-ignore-end", "newLineKind": "lf" }, "includes": ["book/src/**/*.md", "*.md"], "excludes": [], "plugins": ["https://plugins.dprint.dev/markdown-0.17.8.wasm"] } ================================================ FILE: examples/.gitignore ================================================ zngur.h *.zng.* ================================================ FILE: examples/char/.gitignore ================================================ generated.h generated.rs ================================================ FILE: examples/char/Cargo.toml ================================================ [package] name = "example-char" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/char/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_char.a ${CXX} -std=c++11 -Werror -I. main.cpp -g -L ../../target/release/ -l example_char ../../target/release/libexample_char.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/char/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_char.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/char/NMakefile ================================================ CXX = cl.exe # MSVC doesn't support earlier that c++14 CXXFLAGS = /W4 /DEBUG /EHsc /std:c++14 WINLIBS = ntdll.lib EXAMPLE_NAME = char GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/char/expected_output.txt ================================================ Rust received char: 'A' (U+0041) Rust received char: 'é' (U+00E9) Rust received char: 'Z' (U+005A) Rust received char: '�' (U+FFFD) ================================================ FILE: examples/char/main.cpp ================================================ #include "./generated.h" using ::operator""_rs; int main() { // Pass a char literal directly using the _rs suffix rust::crate::CharPrinter::print(U'A'_rs); // Pass a char from a char32_t variable char32_t c = U'\u00E9'; // é rust::crate::CharPrinter::print(operator""_rs(c)); // Use a char in a predicate if (rust::crate::CharPrinter::is_alphabetic(U'z'_rs)) { rust::crate::CharPrinter::print(rust::crate::CharPrinter::to_uppercase(U'z'_rs)); } // Invalid chars are replaced with U+FFFD char32_t invalid = 0xD800; // Surrogate code point rust::crate::CharPrinter::print(operator""_rs(invalid)); return 0; } ================================================ FILE: examples/char/main.zng ================================================ type char { #layout(size = 4, align = 4); wellknown_traits(Copy); } type bool { #layout(size = 1, align = 1); wellknown_traits(Copy); } type crate::CharPrinter { #layout(size = 0, align = 1); fn print(char); fn is_alphabetic(char) -> bool; fn to_uppercase(char) -> char; } ================================================ FILE: examples/char/src/lib.rs ================================================ #[rustfmt::skip] mod generated; struct CharPrinter; impl CharPrinter { fn print(c: char) { println!("Rust received char: '{}' (U+{:04X})", c, c as u32); } fn is_alphabetic(c: char) -> bool { c.is_alphabetic() } fn to_uppercase(c: char) -> char { c.to_uppercase().next().unwrap_or(c) } } ================================================ FILE: examples/conditional/.gitignore ================================================ generated.h generated.rs generated*.rs generated.cpp history.txt b.out ================================================ FILE: examples/conditional/Cargo.toml ================================================ [package] name = "example-conditional" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] [features] default = [] float-values = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/conditional/Makefile ================================================ both:: a.out b.out a.out: main.cpp include/float/generated.h src/lib.rs ../../target/release/libexample_conditional.a ${CXX} -std=c++20 -Werror main.cpp -g -I include/float -o a.out -L../../target/release -l example_conditional b.out: main.cpp include/int/generated.h src/lib.rs ../../target/debug/libexample_conditional.a ${CXX} -std=c++20 -Werror main.cpp -g -I include/int -o b.out -L../../target/debug -l example_conditional ../../target/release/libexample_conditional.a: ./src/generated_float.rs cargo build --release -F "float-values" ../../target/debug/libexample_conditional.a: ./src/generated_int.rs cargo build include/float/generated.h ./src/generated_float.rs: main.zng mkdir -p include/float cd ../../zngur-cli && cargo run g -i ../examples/conditional/main.zng --h-file ../examples/conditional/include/float/generated.h --rs-file ../examples/conditional/src/generated_float.rs -F "float-values" --load-cfg-from-rustc --use-cargo-rustc --profile=release --package example-conditional --crate-name "crate" include/int/generated.h ./src/generated_int.rs: main.zng mkdir -p include/int cd ../../zngur-cli && cargo run g -i ../examples/conditional/main.zng --h-file ../examples/conditional/include/int/generated.h --rs-file ../examples/conditional/src/generated_int.rs --load-cfg-from-rustc --crate-name "crate" .PHONY: ../../target/release/libexample_conditional.a ../../target/debug/libexample_conditional.a include/float/generated.h include/int/generated.h clean clean: rm -f include/float/generated.h include/int/generated.h generated.cpp src/generated_float.rs src/generated_int.rs a.out b.out actual_output.txt ================================================ FILE: examples/conditional/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib Userenv.lib Ws2_32.lib EXAMPLE_NAME = conditional GENERATED_A = include/float/generated.h ./src/generated_float.rs GENERATED_B = include/int/generated.h ./src/generated_int.rs RUSTLIB_PATH_A = ../../target/release/ RUSTLIB_PATH_B = ../../target/debug/ RUSTLIB = example_$(EXAMPLE_NAME).lib all : a.exe b.exe a.exe : main.cpp src/lib.rs $(GENERATED_A) $(RUSTLIB_PATH_A)/$(RUSTLIB) $(CXX) $(CXXFLAGS) /c main.cpp /I include/float /Fo: "a" $(CXX) $(CXXFLAGS) a.obj /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH_A) b.exe : main.cpp src/lib.rs $(GENERATED_B) $(RUSTLIB_PATH_B)/$(RUSTLIB) $(CXX) $(CXXFLAGS) /c main.cpp /I include/int /Fo: "b" $(CXX) $(CXXFLAGS) b.obj /Fe:b.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH_B) $(RUSTLIB_PATH_A)/$(RUSTLIB) : cargo build --release -F "float-values" $(RUSTLIB_PATH_B)/$(RUSTLIB) : cargo build $(GENERATED_A) : main.zng IF NOT EXIST "include" mkdir include IF NOT EXIST "include\float" mkdir "include\float" cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --h-file ../examples/$(EXAMPLE_NAME)/include/float/generated.h --rs-file ../examples/$(EXAMPLE_NAME)/src/generated_float.rs -F "float-values" --load-cfg-from-rustc --use-cargo-rustc --profile=release --package example-conditional --crate-name "crate" $(GENERATED_B) : main.zng IF NOT EXIST "include" mkdir include IF NOT EXIST "include\int" mkdir "include\int" cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --h-file ../examples/$(EXAMPLE_NAME)/include/int/generated.h --rs-file ../examples/$(EXAMPLE_NAME)/src/generated_int.rs --load-cfg-from-rustc --crate-name "crate" clean : - del /f /q include\float\generated.h include\int\generated.h generated.cpp src\generated_float.rs src\generated_int.rs a.exe b.exe a.obj b.obj actual_output.txt 2>nul ================================================ FILE: examples/conditional/README.md ================================================ # Example: Conditional A example, used to demonstrate conditional compilation To run this example: ``` make ./a.out ``` ================================================ FILE: examples/conditional/expected_output.txt ================================================ KVPair(size = 32, align = 8){foo : 1.000000} [main.cpp:44] pair = KeyValuePair[f64] { key: "foo", value: 1.0, } KVPair(size = 32, align = 8){bar : 2.000000} [main.cpp:44] pair = KeyValuePair[f64] { key: "bar", value: 2.0, } KVPair(size = 32, align = 8){baz : 3.000000} [main.cpp:44] pair = KeyValuePair[f64] { key: "baz", value: 3.0, } KVPair(size = 32, align = 8){abc : 4.000000} [main.cpp:44] pair = KeyValuePair[f64] { key: "abc", value: 4.0, } KVPair(size = 32, align = 8){foo : 1} [main.cpp:44] pair = KeyValuePair[i32] { key: "foo", value: 1, }(with debug_assertions) KVPair(size = 32, align = 8){bar : 2} [main.cpp:44] pair = KeyValuePair[i32] { key: "bar", value: 2, }(with debug_assertions) KVPair(size = 32, align = 8){baz : 3} [main.cpp:44] pair = KeyValuePair[i32] { key: "baz", value: 3, }(with debug_assertions) KVPair(size = 32, align = 8){abc : 4} [main.cpp:44] pair = KeyValuePair[i32] { key: "abc", value: 4, }(with debug_assertions) ================================================ FILE: examples/conditional/main.cpp ================================================ #include #include #include #include #include #include #include "generated.h" // Rust values are available in the `::rust` namespace from their absolute path // in Rust template using Vec = rust::std::vec::Vec; template using Option = rust::std::option::Option; using KVPair = rust::crate::KeyValuePair; #ifdef ZNGUR_CFG_FEATURE_FLOAT_VALUES using KVPairValue_T = ::std::double_t; #else using KVPairValue_T = ::std::int32_t; #endif int main() { auto s = Vec::new_(); std::vector, int>> pairs = { {"foo"_rs, 1}, {"bar"_rs, 2}, {"baz"_rs, 3}, {"abc"_rs, 4}}; for (const auto &[key, value] : pairs) { s.push(KVPair{key.to_owned(), static_cast(value)}); } auto it = s.iter(); for (auto next = it.next(); next.matches_Some(); next = it.next()) { auto pair = next.unwrap(); rust::Ref key = pair.key; KVPairValue_T value = pair.value; std::cout << "KVPair(size = " << KVPair::self_size() << ", align = " << KVPair::self_align() << "){" << std::string_view( reinterpret_cast(key.as_str().as_ptr()), key.len()) << " : " << std::to_string(value) << "}\n"; zngur_dbg(pair); } } ================================================ FILE: examples/conditional/main.zng ================================================ #convert_panic_to_exception #unstable(cfg_if) type str { wellknown_traits(?Sized, Debug); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; fn to_owned(&self) -> ::std::string::String; } type ::std::string::String { #layout(size = 24, align = 8); wellknown_traits(Debug); fn clone(&self) -> ::std::string::String; fn push_str(&mut self, &str); fn len(&self) -> usize; fn as_str(&self) -> &str; } mod crate { type KeyValuePair { #layout(size = 32, align = 8); wellknown_traits(Debug); #if cfg!(feature."float-values") { constructor { key: ::std::string::String, value: f64 }; } #else { constructor { key: ::std::string::String, value: i32 }; } fn self_size() -> usize; fn self_align() -> usize; field key (offset = 0, type = ::std::string::String ); #if cfg!(feature."float-values") { field value (offset = 24, type = f64 ); } #else { field value (offset = 24, type = i32 ); } } } mod ::std { type option::Option { #layout(size = 16, align = 8); constructor None; constructor Some(usize); fn unwrap(self) -> usize; } type option::Option { #layout(size = 32, align = 8); constructor None; constructor Some(crate::KeyValuePair); fn unwrap(self) -> crate::KeyValuePair; } type option::Option<&crate::KeyValuePair> { #layout(size = 8, align = 8); wellknown_traits(Copy); constructor None; constructor Some(&crate::KeyValuePair); fn unwrap(self) -> &crate::KeyValuePair; } mod vec { type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec; fn push(&mut self, crate::KeyValuePair); fn get(&self, usize) -> ::std::option::Option<&crate::KeyValuePair> deref [crate::KeyValuePair]; fn iter(&self) -> ::std::slice::Iter deref [crate::KeyValuePair]; } } mod slice { type Iter { #layout(size = 16, align = 8); fn len(&self) -> usize; fn size_hint(&self) -> (usize, ::std::option::Option); fn count(self) -> usize; fn next(&mut self) -> ::std::option::Option<&crate::KeyValuePair>; } } } type (usize, ::std::option::Option) { #layout(size = 24, align = 8); wellknown_traits(Debug); field 0 (offset = 0, type = usize); field 1 (offset = 8, type = ::std::option::Option ); } ================================================ FILE: examples/conditional/src/lib.rs ================================================ /// only in place to allow both build to build sid by side in CI #[rustfmt::skip] #[cfg(feature = "float-values")] mod generated_float; #[rustfmt::skip] #[cfg(not(feature = "float-values"))] mod generated_int; struct KeyValuePair { pub key: String, #[cfg(feature = "float-values")] pub value: f64, #[cfg(not(feature = "float-values"))] pub value: i32, } impl KeyValuePair { fn self_size() -> usize { std::mem::size_of::() } fn self_align() -> usize { std::mem::align_of::() } } #[cfg(debug_assertions)] impl std::fmt::Debug for KeyValuePair { #[cfg(feature = "float-values")] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "KeyValuePair[f64] {{ key: {:?}, value: {:?}, }}(with debug_assertions)", &self.key, &self.value ) } #[cfg(not(feature = "float-values"))] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "KeyValuePair[i32] {{ key: {:?}, value: {:?}, }}(with debug_assertions)", &self.key, &self.value ) } } #[cfg(not(debug_assertions))] impl std::fmt::Debug for KeyValuePair { #[cfg(feature = "float-values")] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "KeyValuePair[f64] {{ key: {:?}, value: {:?}, }}", &self.key, &self.value ) } #[cfg(not(feature = "float-values"))] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "KeyValuePair[i32] {{ key: {:?}, value: {:?}, }}", &self.key, &self.value ) } } ================================================ FILE: examples/cxx_demo/.gitignore ================================================ generated.h generated.rs generated.cpp history.txt ================================================ FILE: examples/cxx_demo/Cargo.toml ================================================ [package] name = "example-cxx_demo" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [build-dependencies] cc = "1.0" build-rs = "0.1.2" zngur = { path = "../../zngur" } ================================================ FILE: examples/cxx_demo/README.md ================================================ # Example: CXX demo This example tries to replicate [the CXX demo](https://github.com/dtolnay/cxx/tree/master/demo) using Zngur. It tries to keep it as close as possible to the original CXX example, so it doesn't use Zngur features that may make the code easier, more readable, and more performant. To see an example that uses all Zngur features in C++ to Rust codes, go to the osmium example. To run this example: ``` cargo run ``` ================================================ FILE: examples/cxx_demo/blobstore.cpp ================================================ #include #include #include #include #include "./generated.h" class BlobStore : public rust::crate::BlobStoreTrait { class Impl { friend BlobStore; using Blob = struct { std::string data; std::set tags; }; std::unordered_map blobs; }; public: uint64_t put(rust::RefMut buf) override { std::string contents; // Traverse the caller's chunk iterator. // // In reality there might be sophisticated batching of chunks and/or // parallel upload implemented by the blob_store's C++ client. while (true) { auto chunk = buf.next_chunk(); if (chunk.len() == 0) { break; } contents.append(reinterpret_cast(chunk.as_ptr()), chunk.len()); } // Insert into map and provide caller the handle. auto blob_id = std::hash{}(contents); impl.blobs[blob_id] = {std::move(contents), {}}; return blob_id; } rust::Unit tag(::uint64_t blob_id, rust::Ref tag) override { impl.blobs[blob_id].tags.emplace((char *)tag.as_ptr(), tag.len()); return rust::Unit{}; } rust::crate::BlobMetadata metadata(::uint64_t blob_id) override { rust::crate::BlobMetadata r = rust::crate::BlobMetadata::default_(); auto blob = impl.blobs.find(blob_id); if (blob != impl.blobs.end()) { r.set_size(blob->second.data.size()); std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), [&](auto &t) { r.push_tag(reinterpret_cast(t.c_str())); }); } return r; } private: Impl impl; }; rust::Box> rust::exported_functions::new_blob_store_client() { return rust::Box>::make_box< BlobStore>(); } ================================================ FILE: examples/cxx_demo/build.rs ================================================ #[cfg(not(target_os = "windows"))] use std::env; use zngur::Zngur; fn main() { build::rerun_if_changed("main.zng"); build::rerun_if_changed("blobstore.cpp"); build::rerun_if_changed("src/"); build::rerun_if_env_changed("CXX"); #[cfg(not(target_os = "windows"))] let cxx = env::var("CXX").unwrap_or("c++".to_owned()); let crate_dir = build::cargo_manifest_dir(); Zngur::from_zng_file(crate_dir.join("main.zng")) .with_cpp_file(crate_dir.join("generated.cpp")) .with_h_file(crate_dir.join("generated.h")) .with_rs_file(crate_dir.join("./src/generated.rs")) .with_crate_name("crate") .with_zng_header_in_place() .generate(); let my_build = &mut cc::Build::new(); let my_build = my_build.cpp(true).std("c++17"); #[cfg(not(target_os = "windows"))] my_build.compiler(&cxx); let my_build = || my_build.clone(); my_build().file("generated.cpp").compile("zngur_generated"); my_build().file("blobstore.cpp").compile("blobstore"); } ================================================ FILE: examples/cxx_demo/expected_output.txt ================================================ metadata = BlobMetadata { size: 19, tags: ["rust"] } ================================================ FILE: examples/cxx_demo/main.zng ================================================ type [u8] { wellknown_traits(?Sized); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; } type str { wellknown_traits(?Sized); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; } mod crate { type MultiBuf { #layout(size = 32, align = 8); fn next_chunk(&mut self) -> &[u8]; } type BlobMetadata { #layout(size = 32, align = 8); fn default() -> BlobMetadata; fn set_size(&mut self, usize); fn push_tag(&mut self, *const i8); } trait BlobStoreTrait { fn put(&self, &mut MultiBuf) -> u64; fn tag(&self, u64, &str); fn metadata(&self, u64) -> BlobMetadata; } type Box { #layout(size = 16, align = 8); fn put(&self, &mut MultiBuf) -> u64 deref dyn BlobStoreTrait; fn tag(&self, u64, &str) deref dyn BlobStoreTrait; fn metadata(&self, u64) -> BlobMetadata deref dyn BlobStoreTrait; } } extern "C++" { fn new_blob_store_client() -> Box; } ================================================ FILE: examples/cxx_demo/src/main.rs ================================================ #[rustfmt::skip] mod generated; use generated::new_blob_store_client; // An iterator over contiguous chunks of a discontiguous file object. Toy // implementation uses a Vec> but in reality this might be iterating // over some more complex Rust data structure like a rope, or maybe loading // chunks lazily from somewhere. pub struct MultiBuf { chunks: Vec>, pos: usize, } impl MultiBuf { pub fn next_chunk(&mut self) -> &[u8] { let next = self.chunks.get(self.pos); self.pos += 1; next.map_or(&[], Vec::as_slice) } } #[derive(Debug, Default)] struct BlobMetadata { size: usize, tags: Vec, } impl BlobMetadata { fn set_size(&mut self, size: usize) { self.size = size; } fn push_tag(&mut self, c: *const i8) { self.tags.push( String::from_utf8_lossy(unsafe { std::ffi::CStr::from_ptr(c).to_bytes() }).to_string(), ); } } trait BlobStoreTrait { fn put(&self, buf: &mut MultiBuf) -> u64; fn tag(&self, blob_id: u64, tag: &str); fn metadata(&self, blob_id: u64) -> BlobMetadata; } fn main() { let client = new_blob_store_client(); // Upload a blob_. let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; let mut buf = MultiBuf { chunks, pos: 0 }; let blob_id = client.put(&mut buf); // Add a tag. client.tag(blob_id, "rust"); // Read back the tags. let metadata = client.metadata(blob_id); println!("metadata = {:?}", metadata); } ================================================ FILE: examples/cxx_demo_split/.gitignore ================================================ generated.h generated.rs generated.cpp history.txt ================================================ FILE: examples/cxx_demo_split/Cargo.toml ================================================ [package] name = "example-cxx_demo_split" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [build-dependencies] cc = "1.0" build-rs = "0.1.2" zngur = { path = "../../zngur" } ================================================ FILE: examples/cxx_demo_split/README.md ================================================ # Example: CXX demo This example tries to replicate [the CXX demo](https://github.com/dtolnay/cxx/tree/master/demo) using Zngur. It tries to keep it as close as possible to the original CXX example, so it doesn't use Zngur features that may make the code easier, more readable, and more performant. To see an example that uses all Zngur features in C++ to Rust codes, go to the osmium example. To run this example: ``` cargo run ``` ================================================ FILE: examples/cxx_demo_split/blobstore.cpp ================================================ #include #include #include #include #include "./generated.h" class BlobStore : public rust::crate::BlobStoreTrait { class Impl { friend BlobStore; using Blob = struct { std::string data; std::set tags; }; std::unordered_map blobs; }; public: uint64_t put(rust::RefMut buf) override { std::string contents; // Traverse the caller's chunk iterator. // // In reality there might be sophisticated batching of chunks and/or // parallel upload implemented by the blob_store's C++ client. while (true) { auto chunk = buf.next_chunk(); if (chunk.len() == 0) { break; } contents.append(reinterpret_cast(chunk.as_ptr()), chunk.len()); } // Insert into map and provide caller the handle. auto blob_id = std::hash{}(contents); impl.blobs[blob_id] = {std::move(contents), {}}; return blob_id; } rust::Unit tag(::uint64_t blob_id, rust::Ref tag) override { impl.blobs[blob_id].tags.emplace((char *)tag.as_ptr(), tag.len()); return rust::Unit{}; } rust::crate::BlobMetadata metadata(::uint64_t blob_id) override { rust::crate::BlobMetadata r = rust::crate::BlobMetadata::default_(); auto blob = impl.blobs.find(blob_id); if (blob != impl.blobs.end()) { r.set_size(blob->second.data.size()); std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), [&](auto &t) { r.push_tag(reinterpret_cast(t.c_str())); }); } return r; } private: Impl impl; }; rust::Box> rust::exported_functions::new_blob_store_client() { return rust::Box>::make_box< BlobStore>(); } ================================================ FILE: examples/cxx_demo_split/build.rs ================================================ #[cfg(not(target_os = "windows"))] use std::env; use zngur::Zngur; fn main() { build::rerun_if_changed("main.zng"); build::rerun_if_changed("blobstore.cpp"); build::rerun_if_changed("src/"); build::rerun_if_changed("zngur.h"); build::rerun_if_env_changed("CXX"); #[cfg(not(target_os = "windows"))] let cxx = env::var("CXX").unwrap_or("c++".to_owned()); let crate_dir = build::cargo_manifest_dir(); Zngur::from_zng_file(crate_dir.join("main.zng")) .with_cpp_file(crate_dir.join("generated.cpp")) .with_h_file(crate_dir.join("generated.h")) .with_rs_file(crate_dir.join("./src/generated.rs")) .with_crate_name("crate") .with_zng_header("zngur.h") .generate(); let my_build = &mut cc::Build::new(); let my_build = my_build.cpp(true).std("c++17").include("."); #[cfg(not(target_os = "windows"))] my_build.compiler(&cxx); let my_build = || my_build.clone(); my_build().file("generated.cpp").compile("zngur_generated"); my_build().file("blobstore.cpp").compile("blobstore"); } ================================================ FILE: examples/cxx_demo_split/expected_output.txt ================================================ metadata = BlobMetadata { size: 19, tags: ["rust"] } ================================================ FILE: examples/cxx_demo_split/main.zng ================================================ type [u8] { wellknown_traits(?Sized); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; } type str { wellknown_traits(?Sized); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; } mod crate { type MultiBuf { #layout(size = 32, align = 8); fn next_chunk(&mut self) -> &[u8]; } type BlobMetadata { #layout(size = 32, align = 8); fn default() -> BlobMetadata; fn set_size(&mut self, usize); fn push_tag(&mut self, *const i8); } trait BlobStoreTrait { fn put(&self, &mut MultiBuf) -> u64; fn tag(&self, u64, &str); fn metadata(&self, u64) -> BlobMetadata; } type Box { #layout(size = 16, align = 8); fn put(&self, &mut MultiBuf) -> u64 deref dyn BlobStoreTrait; fn tag(&self, u64, &str) deref dyn BlobStoreTrait; fn metadata(&self, u64) -> BlobMetadata deref dyn BlobStoreTrait; } } extern "C++" { fn new_blob_store_client() -> Box; } ================================================ FILE: examples/cxx_demo_split/src/main.rs ================================================ #[rustfmt::skip] mod generated; use generated::new_blob_store_client; // An iterator over contiguous chunks of a discontiguous file object. Toy // implementation uses a Vec> but in reality this might be iterating // over some more complex Rust data structure like a rope, or maybe loading // chunks lazily from somewhere. pub struct MultiBuf { chunks: Vec>, pos: usize, } impl MultiBuf { pub fn next_chunk(&mut self) -> &[u8] { let next = self.chunks.get(self.pos); self.pos += 1; next.map_or(&[], Vec::as_slice) } } #[derive(Debug, Default)] struct BlobMetadata { size: usize, tags: Vec, } impl BlobMetadata { fn set_size(&mut self, size: usize) { self.size = size; } fn push_tag(&mut self, c: *const i8) { self.tags.push( String::from_utf8_lossy(unsafe { std::ffi::CStr::from_ptr(c).to_bytes() }).to_string(), ); } } trait BlobStoreTrait { fn put(&self, buf: &mut MultiBuf) -> u64; fn tag(&self, blob_id: u64, tag: &str); fn metadata(&self, blob_id: u64) -> BlobMetadata; } fn main() { let client = new_blob_store_client(); // Upload a blob_. let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; let mut buf = MultiBuf { chunks, pos: 0 }; let blob_id = client.put(&mut buf); // Add a tag. client.tag(blob_id, "rust"); // Read back the tags. let metadata = client.metadata(blob_id); println!("metadata = {:?}", metadata); } ================================================ FILE: examples/impl_trait/.gitignore ================================================ generated.h generated.rs generated.cpp ================================================ FILE: examples/impl_trait/Cargo.toml ================================================ [package] name = "example-impl-trait" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/impl_trait/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_impl_trait.a ${CXX} -std=c++20 -Werror main.cpp -g -L ../../target/release/ -l example_impl_trait ../../target/release/libexample_impl_trait.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/impl_trait/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_impl_trait.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/impl_trait/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib EXAMPLE_NAME = impl_trait GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/impl_trait/README.md ================================================ # Example: Regression test 1 A example, used to check previous Zngur problems in CI. To run this example: ``` make ./a.out ``` ================================================ FILE: examples/impl_trait/expected_output.txt ================================================ Print debug -- 5 Print debug -- "hello" Print debug -- 4 Print debug -- "foo" Print debug -- "foo" Print debug -- "Rust futures are lazy" Async func 1 Future done with result 43 Async func 2 Future done with result 44 Print debug -- "Before calling impl_future" Before async block Print debug -- "Before polling impl_future" Inside async block Future done with result 45 ================================================ FILE: examples/impl_trait/main.cpp ================================================ #include #include #include "./generated.h" using DynDebug = rust::Dyn; int main() { rust::crate::argument_position_impl_trait(5); rust::crate::argument_position_impl_trait("hello"_rs.to_owned()); rust::crate::argument_position_impl_trait( rust::crate::return_position_impl_trait()); rust::Box elem = rust::crate::both_impl_trait("foo"_rs); rust::crate::argument_position_impl_trait(elem.deref()); rust::crate::argument_position_impl_trait(std::move(elem)); auto future = rust::crate::async_func1(); rust::crate::argument_position_impl_trait( "Rust futures are lazy"_rs.to_owned()); rust::crate::busy_wait_future(std::move(future)); rust::crate::busy_wait_future(rust::crate::async_func2()); rust::crate::argument_position_impl_trait( "Before calling impl_future"_rs.to_owned()); future = rust::crate::impl_future(); rust::crate::argument_position_impl_trait( "Before polling impl_future"_rs.to_owned()); rust::crate::busy_wait_future(std::move(future)); } ================================================ FILE: examples/impl_trait/main.zng ================================================ #convert_panic_to_exception type str { wellknown_traits(?Sized, Debug); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; fn to_owned(&self) -> ::std::string::String; } type dyn ::std::fmt::Debug { wellknown_traits(?Sized, Debug); } type Box { #layout(size = 16, align = 8); fn deref(&self) -> &dyn ::std::fmt::Debug use ::std::ops::Deref; } type Box> { #layout(size = 16, align = 8); } type ::std::string::String { #layout(size = 24, align = 8); wellknown_traits(Debug); fn clone(&self) -> ::std::string::String; fn push_str(&mut self, &str); fn len(&self) -> usize; } mod crate { fn argument_position_impl_trait(i32); fn argument_position_impl_trait(::std::string::String); fn argument_position_impl_trait(Box); fn argument_position_impl_trait(&dyn ::std::fmt::Debug); fn return_position_impl_trait() -> impl ::std::fmt::Debug; fn both_impl_trait(&str) -> impl ::std::fmt::Debug; fn async_func1() -> impl ::std::future::Future; async fn async_func2() -> i32; async fn impl_future() -> i32; fn busy_wait_future(Box>) -> i32; } ================================================ FILE: examples/impl_trait/src/lib.rs ================================================ use std::{ fmt::Debug, task::{Context, Waker}, }; #[rustfmt::skip] mod generated; fn argument_position_impl_trait(arg: impl Debug) { println!("Print debug -- {arg:?}"); } fn return_position_impl_trait() -> impl Debug { 4 } fn both_impl_trait(input: impl Debug) -> impl Debug { input } async fn async_func1() -> i32 { println!("Async func 1"); 43 } async fn async_func2() -> i32 { println!("Async func 2"); 44 } fn impl_future() -> impl Future { println!("Before async block"); async { println!("Inside async block"); 45 } } fn busy_wait_future(fut: Box>) -> T { let mut fut = Box::into_pin(fut); let waker = Waker::noop(); let mut cx = Context::from_waker(waker); loop { match fut.as_mut().poll(&mut cx) { std::task::Poll::Ready(r) => { println!("Future done with result {r:?}"); return r; } std::task::Poll::Pending => (), } } } ================================================ FILE: examples/memory_management/.gitignore ================================================ generated.h generated.rs generated.cpp ================================================ FILE: examples/memory_management/Cargo.toml ================================================ [package] name = "example-memory_management" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/memory_management/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_memory_management.a ${CXX} -std=c++17 -Werror main.cpp generated.cpp -g -L ../../target/release/ -l example_memory_management ../../target/release/libexample_memory_management.a: cargo build --release generated.h generated.cpp ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/memory_management/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_memory_management.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/memory_management/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib EXAMPLE_NAME = memory_management GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp generated.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj generated.obj actual_output.txt 2>nul ================================================ FILE: examples/memory_management/README.md ================================================ # Example: Memory management Explains the corner cases of memory management by code. To run this example: ``` make ./a.out ``` ================================================ FILE: examples/memory_management/expected_output.txt ================================================ Checkpoint 1 PrintOnDrop(B) has been dropped Checkpoint 2 Checkpoint 3 Checkpoint 4 PrintOnDrop(A) has been dropped Checkpoint 5 PrintOnDrop(cpp_V3) has been dropped PrintOnDrop(cpp_V2) has been dropped Checkpoint 6 PrintOnDrop(cpp_V1) has been dropped Checkpoint 7 Checkpoint 8 PrintOnDrop(rust_V1) has been dropped PrintOnDrop(rust_V2) has been dropped Checkpoint 9 PrintOnDrop(rust_V1) has been dropped PrintOnDrop(rust_V2) has been dropped Checkpoint 10 Checkpoint 11 PrintOnDrop(P) has been dropped PrintOnDrop(P) has been dropped Checkpoint 12 PrintOnDrop(P) has been dropped PrintOnDrop(Q) has been dropped Checkpoint 13 PrintOnDrop(Q) has been dropped Checkpoint 14 Checkpoint 15 Checkpoint 16 PrintOnDrop(P2) has been dropped PrintOnDrop(P2) has been dropped Checkpoint 17 PrintOnDrop(P2) has been dropped PrintOnDrop(Q2) has been dropped Checkpoint 18 Checkpoint 19 PrintOnDrop(Q2) has been dropped Checkpoint 20 Checkpoint 21 PrintOnDrop(A) has been dropped Checkpoint 22 thread panicked at examples/memory_management/src/lib.rs:30:9: consume_and_panic executed with value B note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace PrintOnDrop(B) has been dropped PrintOnDrop(A) has been dropped Checkpoint 24 Checkpoint 25 Checkpoint 26 PrintOnDrop(first) has been dropped PrintOnDrop(second) has been dropped Checkpoint 27 Checkpoint 28 PrintOnDrop(elem1) has been dropped Checkpoint 29 PrintOnDrop(elem1) has been dropped Checkpoint 30 PrintOnDrop(elem1) has been dropped PrintOnDrop(elem2) has been dropped Checkpoint 31 [main.cpp:113] PrintOnDrop("dbg_A"_rs) = PrintOnDrop( "dbg_A", ) Checkpoint 32 [main.cpp:115] std::move(p1) = PrintOnDrop( "dbg_A", ) Checkpoint 33 [main.cpp:117] p2 = PrintOnDrop( "dbg_A", ) Checkpoint 34 PrintOnDrop(dbg_A) has been dropped Checkpoint 35 Checkpoint 36 Checkpoint 37 PrintOnDrop(option_B) has been dropped Checkpoint 38 Checkpoint 39 PrintOnDrop(option_A) has been dropped Checkpoint 40 Checkpoint 41 Checkpoint 42 PrintOnDrop(elem1) has been dropped Checkpoint 42 PrintOnDrop(elem2) has been dropped Checkpoint 42 PrintOnDrop(elem3) has been dropped Checkpoint 43 Checkpoint 44 PrintOnDrop(field_0) has been dropped PrintOnDrop(field_2) has been dropped Checkpoint 45 PrintOnDrop(C) has been dropped ================================================ FILE: examples/memory_management/main.cpp ================================================ #include #include #include #include "./generated.h" template using Vec = rust::std::vec::Vec; template using Option = rust::std::option::Option; template using BoxDyn = rust::Box>; template using RmDyn = rust::RefMut>; using namespace rust::crate; class CppPrintOnDropHolder : public PrintOnDropConsumer { rust::Unit consume(PrintOnDrop p) override { item = std::move(p); return {}; } PrintOnDrop item; }; int main() { auto p1 = PrintOnDrop("A"_rs); auto p2 = PrintOnDrop("B"_rs); auto p3 = PrintOnDrop("C"_rs); std::cout << "Checkpoint 1" << std::endl; p2 = std::move(p1); std::cout << "Checkpoint 2" << std::endl; { std::cout << "Checkpoint 3" << std::endl; PrintOnDrop p{std::move(p2)}; std::cout << "Checkpoint 4" << std::endl; } { std::vector vec1; vec1.emplace_back("cpp_V1"_rs); vec1.emplace_back("cpp_V2"_rs); vec1.emplace_back("cpp_V3"_rs); std::cout << "Checkpoint 5" << std::endl; vec1.pop_back(); vec1.pop_back(); std::cout << "Checkpoint 6" << std::endl; } { std::cout << "Checkpoint 7" << std::endl; Vec vec2 = Vec::new_(); vec2.push(PrintOnDrop("rust_V1"_rs)); vec2.push(PrintOnDrop("rust_V2"_rs)); std::cout << "Checkpoint 8" << std::endl; vec2.clone(); // Clone and drop immediately std::cout << "Checkpoint 9" << std::endl; } { CppPrintOnDropHolder c; { std::cout << "Checkpoint 10" << std::endl; auto holder = BoxDyn::make_box( std::move(c)); std::cout << "Checkpoint 11" << std::endl; consume_n_times(holder.deref_mut(), "P"_rs, 3); std::cout << "Checkpoint 12" << std::endl; consume_n_times(holder.deref_mut(), "Q"_rs, 2); std::cout << "Checkpoint 13" << std::endl; } std::cout << "Checkpoint 14" << std::endl; } { CppPrintOnDropHolder c; { std::cout << "Checkpoint 15" << std::endl; auto holder = RmDyn(c); std::cout << "Checkpoint 16" << std::endl; consume_n_times(holder, "P2"_rs, 3); std::cout << "Checkpoint 17" << std::endl; consume_n_times(holder, "Q2"_rs, 2); std::cout << "Checkpoint 18" << std::endl; } std::cout << "Checkpoint 19" << std::endl; } std::cout << "Checkpoint 20" << std::endl; try { PrintOnDrop a{"A"_rs}; std::cout << "Checkpoint 21" << std::endl; consume_and_panic(a.clone(), false); std::cout << "Checkpoint 22" << std::endl; consume_and_panic("B"_rs, true); std::cout << "Checkpoint 23" << std::endl; } catch (rust::Panic e) { std::cout << "Checkpoint 24" << std::endl; } { std::cout << "Checkpoint 25" << std::endl; PrintOnDropPair p{"first"_rs, "second"_rs}; std::cout << "Checkpoint 26" << std::endl; } { std::cout << "Checkpoint 27" << std::endl; Vec vec2 = Vec::new_(); vec2.push(PrintOnDrop("elem1"_rs)); vec2.push(PrintOnDrop("elem2"_rs)); std::cout << "Checkpoint 28" << std::endl; vec2.get(0).unwrap().clone(); { auto vec_slice = vec2.deref(); auto tmp = vec_slice.get(0).unwrap().clone(); std::cout << "Checkpoint 29" << std::endl; } std::cout << "Checkpoint 30" << std::endl; } std::cout << "Checkpoint 31" << std::endl; { auto p1 = zngur_dbg(PrintOnDrop("dbg_A"_rs)); std::cout << "Checkpoint 32" << std::endl; auto p2 = zngur_dbg(std::move(p1)); std::cout << "Checkpoint 33" << std::endl; zngur_dbg(p2); std::cout << "Checkpoint 34" << std::endl; } std::cout << "Checkpoint 35" << std::endl; { auto p1 = Option::Some( PrintOnDrop("option_A"_rs)); std::cout << "Checkpoint 36" << std::endl; auto p2 = Option::Some( PrintOnDrop("option_B"_rs)); std::cout << "Checkpoint 37" << std::endl; p2.take(); std::cout << "Checkpoint 38" << std::endl; p2.take(); std::cout << "Checkpoint 39" << std::endl; } std::cout << "Checkpoint 40" << std::endl; { rust::Ref elems[3] = {"elem1"_rs, "elem2"_rs, "elem3"_rs}; int i = 0; auto iter = rust::std::iter::from_fn( rust::Box>>>::make_box([&] { if (i == 3) { return Option::None(); } return Option::Some( PrintOnDrop(elems[i++])); })); std::cout << "Checkpoint 41" << std::endl; iter.for_each( rust::Box>>::make_box( [](PrintOnDrop p) -> rust::Unit { std::cout << "Checkpoint 42" << std::endl; return {}; })); } std::cout << "Checkpoint 43" << std::endl; { auto tuple = rust::Tuple( PrintOnDrop("field_0"_rs), 5, PrintOnDrop("field_2"_rs)); std::cout << "Checkpoint 44" << std::endl; } std::cout << "Checkpoint 45" << std::endl; } ================================================ FILE: examples/memory_management/main.zng ================================================ #convert_panic_to_exception type (crate::PrintOnDrop, i32, crate::PrintOnDrop) { #layout(size = 40, align = 8); } type bool { #layout(size = 1, align = 1); wellknown_traits(Copy); } type str { wellknown_traits(?Sized); } type Box ::std::option::Option> { #layout(size = 16, align = 8); } type Box { #layout(size = 16, align = 8); } mod crate { type PrintOnDropPair { #layout(size = 32, align = 8); constructor { first: PrintOnDrop, second: PrintOnDrop }; } type PrintOnDrop { #layout(size = 16, align = 8); wellknown_traits(Debug); constructor(&str); fn clone(&self) -> PrintOnDrop; } type [PrintOnDrop] { wellknown_traits(?Sized); fn get(&self, usize) -> ::std::option::Option<&crate::PrintOnDrop>; } trait PrintOnDropConsumer { fn consume(&mut self, PrintOnDrop); } type Box { #layout(size = 16, align = 8); fn deref_mut(&mut self) -> &mut dyn PrintOnDropConsumer use ::std::ops::DerefMut; } type dyn PrintOnDropConsumer { wellknown_traits(?Sized); } fn consume_n_times(&mut dyn PrintOnDropConsumer, &str, usize); fn consume_and_panic(PrintOnDrop, bool) -> PrintOnDrop; } mod ::std { mod option { type Option<&crate::PrintOnDrop> { #layout(size = 8, align = 8); fn unwrap(self) -> &crate::PrintOnDrop; } type Option { #layout(size = 16, align = 8); constructor Some(crate::PrintOnDrop); constructor None; fn take(&mut self) -> Option; } } mod iter { type FromFn ::std::option::Option>> { #layout(size = 16, align = 8); fn for_each>(self, Box); } fn from_fn ::std::option::Option>>( Box ::std::option::Option> ) -> FromFn ::std::option::Option>>; } mod vec { type Vec { #layout(size = 24, align = 8); fn new() -> Vec; fn push(&mut self, crate::PrintOnDrop); fn clone(&self) -> Vec; fn get(&self, usize) -> ::std::option::Option<&crate::PrintOnDrop> deref [crate::PrintOnDrop]; fn deref(&self) -> &[crate::PrintOnDrop] use ::std::ops::Deref; } } } ================================================ FILE: examples/memory_management/src/lib.rs ================================================ #[rustfmt::skip] mod generated; #[derive(Debug, Clone)] pub struct PrintOnDrop(&'static str); pub struct PrintOnDropPair { pub first: PrintOnDrop, pub second: PrintOnDrop, } impl Drop for PrintOnDrop { fn drop(&mut self) { println!("PrintOnDrop({}) has been dropped", self.0); } } trait PrintOnDropConsumer { fn consume(&mut self, p: PrintOnDrop); } fn consume_n_times(consumer: &mut dyn PrintOnDropConsumer, name: &'static str, times: usize) { for _ in 0..times { consumer.consume(PrintOnDrop(name)); } } fn consume_and_panic(p: PrintOnDrop, do_panic: bool) -> PrintOnDrop { if do_panic { panic!("consume_and_panic executed with value {}", p.0); } p } ================================================ FILE: examples/multiple_modules/Cargo.toml ================================================ [package] name = "example-multiple-modules" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] [dependencies] packet = { path = "packet" } receiver = { path = "receiver" } aggregation = { path = "aggregation" } processor = { path = "processor" } ================================================ FILE: examples/multiple_modules/Makefile ================================================ ZNGUR_CLI = cd ../../zngur-cli && cargo run a.out: main.cpp aggregation.a packet.zng.h receiver.zng.h aggregation.zng.h processor.zng.h zngur.h ../../target/release/libexample_multiple_modules.a ${CXX} -std=c++11 -Werror -I. -Iaggregation main.cpp aggregation.a -g -L ../../target/release/ -l example_multiple_modules ../../target/release/libexample_multiple_modules.a: cargo build --release aggregation.a: aggregation/impls.cpp aggregation/generated.cpp aggregation.zng.h zngur.h ${CXX} -c -std=c++11 -Werror -I. -Iaggregation -o aggregation/generated.o aggregation/generated.cpp ${CXX} -c -std=c++11 -Werror -I. -Iaggregation -o aggregation/impls.o aggregation/impls.cpp ld -r -o aggregation.a aggregation/impls.o aggregation/generated.o packet.zng.h packet/src/packet.zng.rs: packet/packet.zng ${ZNGUR_CLI} g ../examples/multiple_modules/packet/packet.zng \ --h-file=../examples/multiple_modules/packet.zng.h \ --rs-file=../examples/multiple_modules/packet/src/packet.zng.rs \ --crate-name "packet" receiver.zng.h receiver/src/receiver.zng.rs: receiver/receiver.zng packet.zng.h ${ZNGUR_CLI} g ../examples/multiple_modules/receiver/receiver.zng \ --h-file=../examples/multiple_modules/receiver.zng.h \ --rs-file=../examples/multiple_modules/receiver/src/receiver.zng.rs \ --crate-name "receiver" aggregation.zng.h aggregation/src/aggregation.zng.rs aggregation/generated.cpp: aggregation/aggregation.zng packet.zng.h ${ZNGUR_CLI} g ../examples/multiple_modules/aggregation/aggregation.zng \ --h-file=../examples/multiple_modules/aggregation.zng.h \ --rs-file=../examples/multiple_modules/aggregation/src/aggregation.zng.rs \ --crate-name "aggregation" processor.zng.h processor/src/processor.zng.rs: processor/processor.zng receiver.zng.h aggregation.zng.h ${ZNGUR_CLI} g ../examples/multiple_modules/processor/processor.zng \ --h-file=../examples/multiple_modules/processor.zng.h \ --rs-file=../examples/multiple_modules/processor/src/processor.zng.rs \ --crate-name "processor" zngur.h: ${ZNGUR_CLI} -- make-zng-header ../examples/multiple_modules/zngur.h .PHONY: ../../target/release/libexample_multiple_modules.a clean run run: a.out ./a.out clean: rm -f *.zng.h packet/src/*.zng.rs receiver/src/*.zng.rs aggregation/generated.cpp aggregation/src/*.zng.rs aggregation/*.o processor/src/*.zng.rs zngur.h a.out *.o *.a ================================================ FILE: examples/multiple_modules/aggregation/Cargo.toml ================================================ [package] name = "aggregation" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["rlib"] [dependencies] packet = { path = "../packet" } ================================================ FILE: examples/multiple_modules/aggregation/aggregation.zng ================================================ import "packet.zng"; #cpp_additional_includes " #include " type crate::StatsAccumulator { #layout(size = 16, align = 8); constructor(ZngurCppOpaqueOwnedObject); #cpp_value "0" "::cpp_stats::StatsAccumulator"; } extern "C++" { impl crate::StatsAccumulator { fn create() -> crate::StatsAccumulator; fn add_packet(&mut self, packet::Packet); fn print_report(&self); } } ================================================ FILE: examples/multiple_modules/aggregation/impls.cpp ================================================ #include #include #include using rust::aggregation::StatsAccumulator; using rust::packet::Packet; // We need to implement the methods declared in aggregation.zng StatsAccumulator rust::Impl::create() { return StatsAccumulator( rust::ZngurCppOpaqueOwnedObject::build()); } rust::Unit rust::Impl::add_packet(rust::RefMut self, Packet p) { self.cpp().add_packet(p); return {}; } rust::Unit rust::Impl::print_report(rust::Ref self) { self.cpp().print_report(); return {}; } ================================================ FILE: examples/multiple_modules/aggregation/src/lib.rs ================================================ #[rustfmt::skip] #[path = "aggregation.zng.rs"] mod generated; pub use packet::Packet; pub struct StatsAccumulator(pub generated::ZngurCppOpaqueOwnedObject); ================================================ FILE: examples/multiple_modules/aggregation/stats.h ================================================ #pragma once #include #include #include // We include packet.zng.h to use Packet methods #include namespace cpp_stats { class StatsAccumulator { std::vector timestamps; std::vector sizes; public: StatsAccumulator() = default; void add_packet(const rust::packet::Packet &p) { timestamps.push_back(p.timestamp()); sizes.push_back(p.size()); } void print_report() const { uint64_t total_size = std::accumulate(sizes.begin(), sizes.end(), 0ULL); double latency_avg = 0; for (int i = 1; i < timestamps.size(); ++i) { latency_avg += static_cast(timestamps[i] - timestamps[i - 1]) / (timestamps.size() - 1); } std::cout << "LatencyAnalysis Report:" << std::endl; std::cout << "Total Packets: " << timestamps.size() << std::endl; std::cout << "Total Bytes: " << total_size << std::endl; if (!timestamps.empty()) { std::cout << "Average Latency: " << latency_avg << std::endl; } } }; } // namespace cpp_stats ================================================ FILE: examples/multiple_modules/expected_output.txt ================================================ Starting LatencyAnalysis simulation... LatencyAnalysis Report: Total Packets: 5 Total Bytes: 150 Average Latency: 100 ================================================ FILE: examples/multiple_modules/main.cpp ================================================ #include #include #include #include #include int main() { auto processor = rust::std::option::Option::Some( rust::processor::Processor::new_()); auto receiver = rust::std::option::Option::Some( rust::receiver::Receiver::new_()); auto stats = rust::Impl::create(); std::cout << "Starting LatencyAnalysis simulation..." << std::endl; processor.unwrap().run(receiver.unwrap(), stats, 5); rust::Impl::print_report( stats); return 0; } ================================================ FILE: examples/multiple_modules/packet/Cargo.toml ================================================ [package] name = "packet" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["rlib"] [dependencies] ================================================ FILE: examples/multiple_modules/packet/packet.zng ================================================ type crate::Packet { #layout(size = 16, align = 8); // u64 timestamp, u32 size fn new(u64, u32) -> crate::Packet; fn timestamp(&self) -> u64; fn size(&self) -> u32; } ================================================ FILE: examples/multiple_modules/packet/src/lib.rs ================================================ pub struct Packet(pub u64, pub u32); impl Packet { pub fn new(timestamp: u64, size: u32) -> Self { Packet(timestamp, size) } pub fn timestamp(&self) -> u64 { self.0 } pub fn size(&self) -> u32 { self.1 } } #[rustfmt::skip] #[path = "packet.zng.rs"] mod generated; ================================================ FILE: examples/multiple_modules/processor/Cargo.toml ================================================ [package] name = "processor" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["rlib"] [dependencies] receiver = { path = "../receiver" } aggregation = { path = "../aggregation" } ================================================ FILE: examples/multiple_modules/processor/processor.zng ================================================ import "receiver.zng"; import "aggregation.zng"; type crate::Processor { #layout(size = 0, align = 1); fn new() -> crate::Processor; fn run(&self, &mut receiver::Receiver, &mut aggregation::StatsAccumulator, u32); } // This isn't necessary for the example but we want to test that specialization // of the same type across modules works properly (See receiver.zng) type ::std::option::Option { #layout(size = 1, align = 1); constructor None; constructor Some(crate::Processor); fn unwrap(self) -> crate::Processor; } ================================================ FILE: examples/multiple_modules/processor/src/lib.rs ================================================ pub use aggregation::StatsAccumulator; pub use receiver::Receiver; pub struct Processor; impl Processor { pub fn new() -> Self { Processor } pub fn run( &self, receiver: &mut receiver::Receiver, stats: &mut aggregation::StatsAccumulator, count: u32, ) { for _ in 0..count { let packet = receiver.next_packet(); stats.add_packet(packet); } } } #[rustfmt::skip] #[path = "processor.zng.rs"] mod generated; ================================================ FILE: examples/multiple_modules/receiver/Cargo.toml ================================================ [package] name = "receiver" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["rlib"] [dependencies] packet = { path = "../packet" } ================================================ FILE: examples/multiple_modules/receiver/receiver.zng ================================================ import "packet.zng"; type crate::Receiver { #layout(size = 8, align = 8); // count: u64 fn new() -> crate::Receiver; fn next_packet(&mut self) -> packet::Packet; } // This isn't necessary for the example but we want to test that specialization // of the same type across modules works properly (See processor.zng) type ::std::option::Option { #layout(size = 16, align = 8); constructor None; constructor Some(crate::Receiver); fn unwrap(self) -> crate::Receiver; } ================================================ FILE: examples/multiple_modules/receiver/src/lib.rs ================================================ pub use packet::Packet; pub struct Receiver { count: u64, } impl Receiver { pub fn new() -> Self { Receiver { count: 0 } } pub fn next_packet(&mut self) -> Packet { self.count += 1; // Simulate packet: timestamp = count * 100, size = count * 10 Packet::new(self.count * 100, (self.count * 10) as u32) } } #[rustfmt::skip] #[path = "receiver.zng.rs"] mod generated; ================================================ FILE: examples/multiple_modules/src/lib.rs ================================================ // Re-export everything from child crates to ensure symbols are included in the staticlib #![allow(unused)] pub use aggregation::*; pub use packet::*; pub use processor::*; pub use receiver::*; ================================================ FILE: examples/raw_pointer/.gitignore ================================================ generated.h generated.rs generated.cpp ================================================ FILE: examples/raw_pointer/Cargo.toml ================================================ [package] name = "example-raw-pointer" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/raw_pointer/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_raw_pointer.a ${CXX} --std=c++17 -Werror main.cpp -g -L ../../target/release/ -l example_raw_pointer ../../target/release/libexample_raw_pointer.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/raw_pointer/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_raw_pointer.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/raw_pointer/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++17 # c++17 is needed for panic to exceptions WINLIBS = ntdll.lib EXAMPLE_NAME = raw_pointer GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/raw_pointer/README.md ================================================ # Example: Simple A simple example, used as a demo in the main README file. To run this example: ``` make ./a.out ``` ================================================ FILE: examples/raw_pointer/expected_output.txt ================================================ [main.cpp:13] s = [ 2, 7, ] [main.cpp:20] s = [ 2, 7, 3, 10, 2, ] [main.cpp:25] ss = [ [ 2, 7, 3, 10, 2, ], [], ] [main.cpp:32] ss = [ [ 2, 7, 3, 10, 2, ], [], [], [ 2, 7, 3, 10, 2, ], [ 2, 7, 3, 10, 2, ], ] [main.cpp:38] s2 = [ 2, 7, 3, 10, 2, 4, ] [main.cpp:40] ss_ptr.read_ref() = [ 2, 7, 3, 10, 2, ] [main.cpp:43] s3_ref_mut = [ 2000, ] [main.cpp:44] ss = [ [ 2, 7, 3, 10, 2, ], [], [ 2000, ], [ 2, 7, 3, 10, 2, ], ] [main.cpp:48] s4_raw_mut.read_ref() = [ 2000, 5, ] [main.cpp:49] ss = [ [ 2, 7, 3, 10, 2, ], [], [ 2000, 5, ], [ 2, 7, 3, 10, 2, ], ] [main.cpp:52] s4_raw.read_ref() = [ 2000, 5, ] [main.cpp:55] ss_ptr2.offset(2).read_ref() = [ 2000, 5, ] [main.cpp:56] ss_ptr2.offset(4).offset(-2).read_ref() = [ 2000, 5, ] [main.cpp:60] s5_raw_mut.read_ref().to_vec() = [ 10, 20, 3, ] ================================================ FILE: examples/raw_pointer/main.cpp ================================================ #include #include "./generated.h" template using Vec = rust::std::vec::Vec; int main() { Vec s = Vec::new_(); s.push(2); s.push(7); zngur_dbg(s); s.reserve(3); int32_t* s_ptr = s.as_mut_ptr(); s_ptr[2] = 3; s_ptr[3] = 10; s_ptr[4] = 2; s.set_len(5); zngur_dbg(s); Vec> ss = Vec>::new_(); ss.push(s.clone()); ss.push(Vec::new_()); zngur_dbg(ss); ss.reserve(3); rust::RawMut> ss_ptr = ss.as_mut_ptr(); ss_ptr.offset(2).write(Vec::new_()); ss_ptr.offset(3).write(s.clone()); ss_ptr.offset(4).write(s.clone()); ss.set_len(5); zngur_dbg(ss); Vec s2 = ss_ptr.offset(4).read(); ss.set_len(4); s2.push(4); zngur_dbg(s2); zngur_dbg(ss_ptr.read_ref()); auto s3_ref_mut = ss_ptr.offset(2).read_mut(); s3_ref_mut.push(2000); zngur_dbg(s3_ref_mut); zngur_dbg(ss); rust::RawMut> s4_raw_mut = ss.get_mut(2).unwrap(); s4_raw_mut.read_mut().push(5); zngur_dbg(s4_raw_mut.read_ref()); zngur_dbg(ss); rust::Raw> s4_raw = ss.get_mut(2).unwrap(); zngur_dbg(s4_raw.read_ref()); rust::Raw> ss_ptr2 = ss.as_ptr(); zngur_dbg(ss_ptr2.offset(2).read_ref()); zngur_dbg(ss_ptr2.offset(4).offset(-2).read_ref()); std::vector v { 10, 20, 3, 15 }; rust::RawMut> s5_raw_mut { { reinterpret_cast(v.data()), 3 } }; zngur_dbg(s5_raw_mut.read_ref().to_vec()); } ================================================ FILE: examples/raw_pointer/main.zng ================================================ #convert_panic_to_exception type str { wellknown_traits(?Sized); fn to_owned(&self) -> ::std::string::String; } type ::std::string::String { #heap_allocated; } type [i32] { wellknown_traits(?Sized); fn as_ptr(&self) -> *const i32; fn len(&self) -> usize; fn to_vec(&self) -> ::std::vec::Vec; } mod ::std { mod option { type Option<&mut ::std::vec::Vec> { #layout(size = 8, align = 8); fn unwrap(self) -> &mut ::std::vec::Vec; } } mod vec { type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec; fn push(&mut self, i32); fn clone(&self) -> Vec; fn reserve(&mut self, usize); fn set_len(&mut self, usize); fn as_mut_ptr(&mut self) -> *mut i32; } type Vec> { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec>; fn push(&mut self, Vec); fn clone(&self) -> Vec>; fn get_mut(&mut self, usize) -> ::std::option::Option<&mut Vec> deref [Vec]; fn reserve(&mut self, usize); fn set_len(&mut self, usize); fn as_mut_ptr(&mut self) -> *mut Vec; fn as_ptr(&self) -> *const Vec; } } } ================================================ FILE: examples/raw_pointer/src/lib.rs ================================================ #[rustfmt::skip] mod generated; ================================================ FILE: examples/rayon/.gitignore ================================================ generated.h generated.rs generated.cpp history.txt ================================================ FILE: examples/rayon/Cargo.toml ================================================ [package] name = "example-rayon" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rayon = "1.7.0" ================================================ FILE: examples/rayon/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_rayon.a ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_rayon ../../target/release/libexample_rayon.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/rayon/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_rayon.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/rayon/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++14 # c++14 is as low as msvc goes WINLIBS = ntdll.lib EXAMPLE_NAME = rayon GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/rayon/README.md ================================================ # Example: Rayon Calculates the sum and the number of prime numbers in `1..10000000` with multiple cores using [rayon](https://github.com/rayon-rs/rayon). To run this example: ``` make ./a.out ``` ================================================ FILE: examples/rayon/expected_output.txt ================================================ Sum = 50000005000000 Count of primes = 664579 ================================================ FILE: examples/rayon/main.cpp ================================================ #include #include #include #include #include #include "./generated.h" template using Box = rust::Box; template using Ref = rust::Ref; template using Dyn = rust::Dyn; template using Fn = rust::Fn; using rust::Bool; using rust::Send; using rust::Sync; bool is_prime(uint64_t v) { if (v < 2) return 0; for (int i = 2; i * i <= v; i += 1) { if (v % i == 0) { return 0; } } return 1; } int main() { std::vector v(10000000); std::iota(v.begin(), v.end(), 1); auto slice = rust::std::slice::from_raw_parts(v.data(), v.size()); auto f = Box, Bool>, Sync, Send>>::make_box( [&](Ref x) { return is_prime(*x); }); std::cout << "Sum = " << slice.par_iter().sum() << std::endl; std::cout << "Count of primes = " << slice.par_iter().copied().filter(std::move(f)).count() << std::endl; } ================================================ FILE: examples/rayon/main.zng ================================================ type bool { #layout(size = 1, align = 1); wellknown_traits(Copy); } type ::std::option::Option<&u64> { #layout(size = 8, align = 8); wellknown_traits(Debug, Copy); fn unwrap(self) -> &u64; } type Box bool + Sync + Send> { #layout(size = 16, align = 8); } type ::rayon::iter::Filter<::rayon::iter::Copied<::rayon::slice::Iter>, Box bool + Sync + Send>> { #layout(size = 32, align = 8); fn count(self) -> usize use ::rayon::iter::ParallelIterator; } type ::rayon::slice::Iter { #layout(size = 16, align = 8); fn sum(self) -> u64 use ::rayon::iter::ParallelIterator; fn copied(self) -> ::rayon::iter::Copied<::rayon::slice::Iter> use ::rayon::iter::ParallelIterator; } type ::rayon::iter::Copied<::rayon::slice::Iter> { #layout(size = 16, align = 8); fn filter bool + Sync + Send>>(self, Box bool + Sync + Send>) -> ::rayon::iter::Filter<::rayon::iter::Copied<::rayon::slice::Iter>, Box bool + Sync + Send>> use ::rayon::iter::ParallelIterator; } type [u64] { wellknown_traits(?Sized); fn get(&self, usize) -> ::std::option::Option<&u64>; fn par_iter(&self) -> ::rayon::slice::Iter use ::rayon::iter::IntoParallelRefIterator; } mod ::std::slice { fn from_raw_parts(*const u64, usize) -> &[u64]; } ================================================ FILE: examples/rayon/src/lib.rs ================================================ #[rustfmt::skip] mod generated; ================================================ FILE: examples/rayon_split/.gitignore ================================================ generated.h generated.rs generated.cpp history.txt ================================================ FILE: examples/rayon_split/Cargo.toml ================================================ [package] name = "example-rayon_split" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rayon = "1.7.0" ================================================ FILE: examples/rayon_split/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_rayon_split.a ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_rayon_split -I . ../../target/release/libexample_rayon_split.a: cargo build --release generated.h ./src/generated.rs: main.zng zngur.h cd ../../zngur-cli && cargo run g ../examples/rayon_split/main.zng --crate-name "crate" zngur.h: cd ../../zngur-cli && cargo run h ../examples/rayon_split/zngur.h .PHONY: ../../target/release/libexample_rayon_split.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt zngur.h ================================================ FILE: examples/rayon_split/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /external:I . /external:W0 /std:c++14 # c++14 is as low as msvc goes WINLIBS = ntdll.lib EXAMPLE_NAME = rayon_split GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng zngur.h cd ../../zngur-cli && cargo run g ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" zngur.h: cd ../../zngur-cli && cargo run h ../examples/$(EXAMPLE_NAME)/zngur.h clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/rayon_split/README.md ================================================ # Example: Rayon Calculates the sum and the number of prime numbers in `1..10000000` with multiple cores using [rayon](https://github.com/rayon-rs/rayon). To run this example: ``` make ./a.out ``` ================================================ FILE: examples/rayon_split/expected_output.txt ================================================ Sum = 50000005000000 Count of primes = 664579 ================================================ FILE: examples/rayon_split/main.cpp ================================================ #include #include #include #include #include #include "./generated.h" template using Box = rust::Box; template using Ref = rust::Ref; template using Dyn = rust::Dyn; template using Fn = rust::Fn; using rust::Bool; using rust::Send; using rust::Sync; bool is_prime(uint64_t v) { if (v < 2) return 0; for (int i = 2; i * i <= v; i += 1) { if (v % i == 0) { return 0; } } return 1; } int main() { std::vector v(10000000); std::iota(v.begin(), v.end(), 1); auto slice = rust::std::slice::from_raw_parts(v.data(), v.size()); auto f = Box, Bool>, Sync, Send>>::make_box( [&](Ref x) { return is_prime(*x); }); std::cout << "Sum = " << slice.par_iter().sum() << std::endl; std::cout << "Count of primes = " << slice.par_iter().copied().filter(std::move(f)).count() << std::endl; } ================================================ FILE: examples/rayon_split/main.zng ================================================ type bool { #layout(size = 1, align = 1); wellknown_traits(Copy); } type ::std::option::Option<&u64> { #layout(size = 8, align = 8); wellknown_traits(Debug, Copy); fn unwrap(self) -> &u64; } type Box bool + Sync + Send> { #layout(size = 16, align = 8); } type ::rayon::iter::Filter<::rayon::iter::Copied<::rayon::slice::Iter>, Box bool + Sync + Send>> { #layout(size = 32, align = 8); fn count(self) -> usize use ::rayon::iter::ParallelIterator; } type ::rayon::slice::Iter { #layout(size = 16, align = 8); fn sum(self) -> u64 use ::rayon::iter::ParallelIterator; fn copied(self) -> ::rayon::iter::Copied<::rayon::slice::Iter> use ::rayon::iter::ParallelIterator; } type ::rayon::iter::Copied<::rayon::slice::Iter> { #layout(size = 16, align = 8); fn filter bool + Sync + Send>>(self, Box bool + Sync + Send>) -> ::rayon::iter::Filter<::rayon::iter::Copied<::rayon::slice::Iter>, Box bool + Sync + Send>> use ::rayon::iter::ParallelIterator; } type [u64] { wellknown_traits(?Sized); fn get(&self, usize) -> ::std::option::Option<&u64>; fn par_iter(&self) -> ::rayon::slice::Iter use ::rayon::iter::IntoParallelRefIterator; } mod ::std::slice { fn from_raw_parts(*const u64, usize) -> &[u64]; } ================================================ FILE: examples/rayon_split/src/lib.rs ================================================ #[rustfmt::skip] mod generated; ================================================ FILE: examples/regression_test1/.gitignore ================================================ generated.h generated.rs generated.cpp ================================================ FILE: examples/regression_test1/Cargo.toml ================================================ [package] name = "example-regression-test1" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/regression_test1/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_regression_test1.a ${CXX} -std=c++20 -Werror main.cpp -g -L ../../target/release/ -l example_regression_test1 ../../target/release/libexample_regression_test1.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/regression_test1/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_regression_test1.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/regression_test1/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib EXAMPLE_NAME = regression_test1 GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/regression_test1/README.md ================================================ # Example: Regression test 1 A example, used to check previous Zngur problems in CI. To run this example: ``` make ./a.out ``` ================================================ FILE: examples/regression_test1/expected_output.txt ================================================ Test dbg works for Ref and RefMut -- started [main.cpp:11] v1 = "foo" [main.cpp:13] v2 = "foo" [main.cpp:15] v3 = "foo" [main.cpp:16] v2 = "foo" [main.cpp:17] v4 = "foo" [main.cpp:19] v5 = "foo" [main.cpp:20] "bar"_rs = "bar" [main.cpp:21] v4 = "foobar" Test dbg works for Ref and RefMut -- finished Test fields and constructor work -- started [main.cpp:31] v1 = Foo { field1: 1, field2: "bar", } [main.cpp:32] v1.field2 = "bar" [main.cpp:33] v1.field2.len() = 3 [main.cpp:35] v1 = Foo { field1: 1, field2: "barbaz", } [main.cpp:39] v2 = ( "kkk", Foo { field1: 1, field2: "barbaz", }, ) [main.cpp:40] v2.f0 = "kkk" [main.cpp:41] v2.f1 = Foo { field1: 1, field2: "barbaz", } [main.cpp:42] v2.f1.field2 = "barbaz" [main.cpp:46] v3.f0 = "kkk" [main.cpp:47] v3.f1 = Foo { field1: 1, field2: "barbazxxx", } [main.cpp:48] v3.f1.field2 = "barbazxxx" [main.cpp:51] v3.f1.field2.len() = 9 [main.cpp:55] v4.f0 = "kkk" [main.cpp:56] v4.f1 = Foo { field1: 1, field2: "barbazxxx", } [main.cpp:57] v4.f1.field2 = "barbazxxx" [main.cpp:59] v4.f1.field2.len() = 12 Test fields and constructor work -- finished Test Field* underlying conversions -- started [main.cpp:71] v0 = 42 [main.cpp:75] v1 = "hi" [main.cpp:79] sref.len() = 2 [main.cpp:82] int32_t(pref.f0) = 42 [main.cpp:83] pref.f1.len() = 2 [main.cpp:86] int32_t(pmut.f0) = 42 [main.cpp:88] pmut.f1.len() = 3 Test Field* underlying conversions -- finished Test floats -- started [main.cpp:98] *r1 = 12.3 [main.cpp:100] v1 = 12.3 [main.cpp:105] fvec = [ 42.24, 147.0, ] [main.cpp:106] fvec.get(0) = Some( 42.24, ) [main.cpp:107] fvec.get(2) = None [main.cpp:108] *fvec.get(1).unwrap() = 147 [main.cpp:110] fvec = [ 42.24, 5.43, ] Test floats -- finished Test dyn Fn() with multiple arguments -- started scope passed to dyn Fn -- started Inner function called scope passed to dyn Fn -- finished End of call_dyn_fn_multi_args Test dyn Fn() with multiple arguments -- finished Test Ref> -- started [main.cpp:131] strvec = [ "a str", "foobar", "a third str", ] [main.cpp:132] strvec.get(0) = Some( "a str", ) [main.cpp:133] strvec.get(2) = Some( "a third str", ) [main.cpp:134] *strvec.get(1).unwrap() = "foobar" [main.cpp:136] strvec = [ "a str", "flip flop", "a third str", ] Test Ref> -- finished Test zero-sized type -- started Method call on ZST [main.cpp:143] zst = ZeroSizedType Test zero-sized type -- finished Test nested Ref where T is #heap_allocated and auto field offsets -- started [main.cpp:158] a = TypeA { foo: 10, bar: FieldTypeA { fizz: FieldTypeC { buzz_1: 20, buzz_2: 30, buzz_3: 40, }, }, baz: FieldTypeB { fizz: FieldTypeC { buzz_1: 50, buzz_2: 60, buzz_3: 70, }, }, } [main.cpp:159] ::rust::Ref(a.foo) = 10 [main.cpp:160] ::rust::Ref(a.bar.fizz.buzz_2) = 30 [main.cpp:161] ::rust::RefMut(a.baz.fizz.buzz_3) = 70 [main.cpp:164] ::rust::Ref(a_fa_fizz.buzz_1) = 20 [main.cpp:166] ::rust::Ref(a_fb_fizz.buzz_2) = 60 [main.cpp:177] b = TypeB { foo: 100, bar: FieldTypeA { fizz: FieldTypeC { buzz_1: 200, buzz_2: 300, buzz_3: 400, }, }, baz: FieldTypeB { fizz: FieldTypeC { buzz_1: 500, buzz_2: 600, buzz_3: 700, }, }, } [main.cpp:178] ::rust::Ref(b.foo) = 100 [main.cpp:179] ::rust::Ref(b.bar.fizz.buzz_2) = 300 [main.cpp:180] ::rust::RefMut(b.baz.fizz.buzz_3) = 700 [main.cpp:183] ::rust::Ref(b_fa_fizz.buzz_1) = 200 [main.cpp:185] ::rust::Ref(b_fb_fizz.buzz_2) = 600 [main.cpp:190] fa = FieldTypeA { fizz: FieldTypeC { buzz_1: 21, buzz_2: 31, buzz_3: 41, }, } [main.cpp:191] ::rust::Ref(fa.fizz.buzz_1) = 21 [main.cpp:192] ::rust::Ref(fa.fizz.buzz_3) = 41 [main.cpp:197] fa = FieldTypeA { fizz: FieldTypeC { buzz_1: 21, buzz_2: 31, buzz_3: 41, }, } [main.cpp:198] ::rust::Ref(fb.fizz.buzz_2) = 61 [main.cpp:199] ::rust::Ref(fb.fizz.buzz_3) = 71 Test nested Ref where T is #heap_allocated and auto field offsets -- finished Test #layout_conservative -- started [main.cpp:208] c_layout = ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", } Rust( size = 32 , align = 8 ) c++( size = 48 , align = 8 ) [main.cpp:220] layouts = [ ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", }, ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", }, ] [main.cpp:221] layouts.get(0) = Some( ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", }, ) [main.cpp:222] layouts.get(1) = Some( ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", }, ) [main.cpp:223] layouts.get(1).unwrap() = ConservativeLayoutType { field1: 2.71828, field2: 1000, field3: "Another test string", } [main.cpp:225] layouts = [ ConservativeLayoutType { field1: 3.14159, field2: 42, field3: "A string at some unknown offset", }, ConservativeLayoutType { field1: 2.71828, field2: 10, field3: "Another test string", }, ] Test #layout_conservative -- finished ================================================ FILE: examples/regression_test1/main.cpp ================================================ #include #include #include "./generated.h" void test_dbg_works_for_ref_and_refmut() { auto scope = rust::crate::Scoped::new_("Test dbg works for Ref and RefMut"_rs); rust::Ref v1 = "foo"_rs; zngur_dbg(v1); rust::std::string::String v2 = v1.to_owned(); zngur_dbg(v2); rust::Ref v3 = v2; zngur_dbg(v3); rust::std::string::String v4 = std::move(zngur_dbg(v2)); zngur_dbg(v4); rust::RefMut v5 = v4; zngur_dbg(v5); v5.push_str(zngur_dbg("bar"_rs)); zngur_dbg(v4); } template concept has_push_str = requires(T v, rust::Ref s) { v.push_str(s); }; void test_fields_and_constructor() { auto scope = rust::crate::Scoped::new_("Test fields and constructor work"_rs); rust::crate::Foo v1 = rust::crate::Foo{1, "bar"_rs.to_owned()}; zngur_dbg(v1); zngur_dbg(v1.field2); zngur_dbg(v1.field2.len()); v1.field2.push_str("baz"_rs); zngur_dbg(v1); rust::Tuple v2{ "kkk"_rs.to_owned(), std::move(v1)}; zngur_dbg(v2); zngur_dbg(v2.f0); zngur_dbg(v2.f1); zngur_dbg(v2.f1.field2); v2.f1.field2.push_str("xxx"_rs); rust::Ref> v3 = v2; zngur_dbg(v3.f0); zngur_dbg(v3.f1); zngur_dbg(v3.f1.field2); static_assert(has_push_str); static_assert(!has_push_str); zngur_dbg(v3.f1.field2.len()); rust::RefMut> v4 = v2; zngur_dbg(v4.f0); zngur_dbg(v4.f1); zngur_dbg(v4.f1.field2); v4.f1.field2.push_str("yyy"_rs); zngur_dbg(v4.f1.field2.len()); } void test_field_underlying_conversions() { auto scope = rust::crate::Scoped::new_("Test Field* underlying conversions"_rs); rust::Tuple pair{42, "hi"_rs.to_owned()}; // FieldOwned conversion to Ref and value rust::Ref r0 = pair.f0; int32_t v0 = pair.f0; zngur_dbg(v0); // Types which are not `Copy` cannot support implicit conversion to T. // We must use `.clone()` or similar methods to get a copy. rust::std::string::String v1 = pair.f1.clone(); zngur_dbg(v1); // FieldOwned to Ref and call a method rust::Ref sref = pair.f1; zngur_dbg(sref.len()); rust::Ref> pref = pair; zngur_dbg(int32_t(pref.f0)); zngur_dbg(pref.f1.len()); rust::RefMut> pmut = pair; zngur_dbg(int32_t(pmut.f0)); pmut.f1.push_str("!"_rs); zngur_dbg(pmut.f1.len()); } void test_floats() { auto scope = rust::crate::Scoped::new_("Test floats"_rs); rust::Tuple pair{42.24, 12.3}; // FieldOwned conversion to Ref and value rust::Ref r1 = pair.f1; zngur_dbg(*r1); double v1 = pair.f1; zngur_dbg(v1); rust::std::vec::Vec fvec = rust::std::vec::Vec::new_(); fvec.push(pair.f0); fvec.push(147); zngur_dbg(fvec); zngur_dbg(fvec.get(0)); zngur_dbg(fvec.get(2)); zngur_dbg(*fvec.get(1).unwrap()); *fvec.get_mut(1).unwrap() = 5.43; zngur_dbg(fvec); } void test_dyn_fn_with_multiple_arguments() { auto scope = rust::crate::Scoped::new_("Test dyn Fn() with multiple arguments"_rs); rust::crate::call_dyn_fn_multi_args(rust::Box, rust::Unit>>>::make_box( [](int32_t arg0, rust::crate::Scoped arg1, rust::Ref arg2) { std::cout << "Inner function called" << std::endl; return rust::Unit{}; } )); } void test_refref() { auto scope = rust::crate::Scoped::new_("Test Ref>"_rs); rust::std::vec::Vec> strvec = rust::std::vec::Vec>::new_(); strvec.push("a str"_rs); strvec.push("foobar"_rs); strvec.push("a third str"_rs); zngur_dbg(strvec); zngur_dbg(strvec.get(0)); zngur_dbg(strvec.get(2)); zngur_dbg(*strvec.get(1).unwrap()); *strvec.get_mut(1).unwrap() = "flip flop"_rs; zngur_dbg(strvec); } void test_zero_sized_type() { auto scope = rust::crate::Scoped::new_("Test zero-sized type"_rs); auto zst = rust::crate::ZeroSizedType::new_(); zst.method(); zngur_dbg(zst); } void test_nested_heap_refs_and_auto_field_offset() { auto scope = rust::crate::Scoped::new_("Test nested Ref where T is #heap_allocated and auto field offsets"_rs); auto a = ::rust::crate::TypeA { 10, ::rust::crate::FieldTypeA { ::rust::crate::FieldTypeC { 20, 30, 40 } }, ::rust::crate::FieldTypeB { ::rust::crate::FieldTypeC { 50, 60, 70 } }, }; zngur_dbg(a); zngur_dbg(::rust::Ref(a.foo)); zngur_dbg(::rust::Ref(a.bar.fizz.buzz_2)); zngur_dbg(::rust::RefMut(a.baz.fizz.buzz_3)); auto a_fa_fizz = ::rust::Ref<::rust::crate::FieldTypeC>(a.bar.fizz); zngur_dbg(::rust::Ref(a_fa_fizz.buzz_1)); auto a_fb_fizz = ::rust::Ref<::rust::crate::FieldTypeC>(a.baz.fizz); zngur_dbg(::rust::Ref(a_fb_fizz.buzz_2)); auto b = ::rust::crate::TypeB { 100, ::rust::crate::FieldTypeA { ::rust::crate::FieldTypeC { 200, 300, 400 } }, ::rust::crate::FieldTypeB { ::rust::crate::FieldTypeC { 500, 600, 700 } }, }; zngur_dbg(b); zngur_dbg(::rust::Ref(b.foo)); zngur_dbg(::rust::Ref(b.bar.fizz.buzz_2)); zngur_dbg(::rust::RefMut(b.baz.fizz.buzz_3)); auto b_fa_fizz = ::rust::Ref<::rust::crate::FieldTypeC>(b.bar.fizz); zngur_dbg(::rust::Ref(b_fa_fizz.buzz_1)); auto b_fb_fizz = ::rust::Ref<::rust::crate::FieldTypeC>(b.baz.fizz); zngur_dbg(::rust::Ref(b_fb_fizz.buzz_2)); auto fa = ::rust::crate::FieldTypeA { ::rust::crate::FieldTypeC { 21, 31, 41 } }; zngur_dbg(fa); zngur_dbg(::rust::Ref(fa.fizz.buzz_1)); zngur_dbg(::rust::Ref(fa.fizz.buzz_3)); auto fb = ::rust::crate::FieldTypeB { ::rust::crate::FieldTypeC { 51, 61, 71 } }; zngur_dbg(fa); zngur_dbg(::rust::Ref(fb.fizz.buzz_2)); zngur_dbg(::rust::Ref(fb.fizz.buzz_3)); } void test_conservative_layout() { auto scope = rust::crate::Scoped::new_("Test #layout_conservative"_rs); auto c_layout = ::rust::crate::ConservativeLayoutType { 3.14159, 42, "A string at some unknown offset"_rs.to_owned() }; zngur_dbg(c_layout); std::cout << "Rust( size = " << c_layout.mem_size() << " , align = " << c_layout.mem_align() << " )\n"; std::cout << "c++( size = " << sizeof(c_layout.__zngur_data) << " , align = " << alignof(decltype(c_layout)) << " )\n"; rust::std::vec::Vec<::rust::crate::ConservativeLayoutType> layouts = rust::std::vec::Vec<::rust::crate::ConservativeLayoutType>::new_(); layouts.push(std::move(c_layout)); layouts.push( ::rust::crate::ConservativeLayoutType { 2.71828, 1000, "Another test string"_rs.to_owned() } ); zngur_dbg(layouts); zngur_dbg(layouts.get(0)); zngur_dbg(layouts.get(1)); zngur_dbg(layouts.get(1).unwrap()); *::rust::RefMut(layouts.get_mut(1).unwrap().field2) = 10; zngur_dbg(layouts); } int main() { test_dbg_works_for_ref_and_refmut(); test_fields_and_constructor(); test_field_underlying_conversions(); test_floats(); test_dyn_fn_with_multiple_arguments(); test_refref(); test_zero_sized_type(); test_nested_heap_refs_and_auto_field_offset(); test_conservative_layout(); } ================================================ FILE: examples/regression_test1/main.zng ================================================ #convert_panic_to_exception type bool { #layout(size = 1, align = 1); wellknown_traits(Copy); } type str { wellknown_traits(?Sized, Debug); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; fn to_owned(&self) -> ::std::string::String; } type ::std::string::String { #layout(size = 24, align = 8); wellknown_traits(Debug); fn clone(&self) -> ::std::string::String; fn push_str(&mut self, &str); fn len(&self) -> usize; } type crate::Foo { #layout(size = 32, align = 8); wellknown_traits(Debug); constructor { field1: i32, field2: ::std::string::String }; field field2 (offset = 0, type = ::std::string::String); } type (::std::string::String, crate::Foo) { #layout(size = 56, align = 8); wellknown_traits(Debug); field 0 (offset = 0, type = ::std::string::String); field 1 (offset = 24, type = crate::Foo); } type (i32, ::std::string::String) { #layout(size = 32, align = 8); wellknown_traits(Debug); field 0 (offset = 0, type = i32); field 1 (offset = 8, type = ::std::string::String); } type (f32, f64) { #layout(size = 16, align = 8); wellknown_traits(Debug); field 0 (offset = 0, type = f32); field 1 (offset = 8, type = f64); } mod ::std::option { type Option<&f32> { #layout(size = 8, align = 8); wellknown_traits(Debug, Copy); fn is_some(&self) -> bool; fn unwrap(self) -> &f32; } type Option<&mut f32> { #layout(size = 8, align = 8); wellknown_traits(Debug); fn is_some(&self) -> bool; fn unwrap(self) -> &f32; } type Option<&&str> { #layout(size = 8, align = 8); wellknown_traits(Debug, Copy); fn is_some(&self) -> bool; fn unwrap(self) -> &&str; } type Option<&mut &str> { #layout(size = 8, align = 8); wellknown_traits(Debug); fn is_some(&self) -> bool; fn unwrap(self) -> &mut &str; } type Option<&crate::ConservativeLayoutType> { #layout(size = 8, align = 8); wellknown_traits(Debug, Copy); fn is_some(&self) -> bool; fn unwrap(self) -> &crate::ConservativeLayoutType; } type Option<&mut crate::ConservativeLayoutType> { #layout(size = 8, align = 8); wellknown_traits(Debug); fn is_some(&self) -> bool; fn unwrap(self) -> &mut crate::ConservativeLayoutType; } } mod ::std::vec { type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec; fn get(&self, usize) -> ::std::option::Option<&f32> deref [f32]; fn get_mut(&mut self, usize) -> ::std::option::Option<&mut f32> deref [f32]; fn push(&mut self, f32); } type Vec<&str> { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec<&str>; fn get(&self, usize) -> ::std::option::Option<&&str> deref [&str]; fn get_mut(&mut self, usize) -> ::std::option::Option<&mut &str> deref [&str]; fn push(&mut self, &str); } type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec; fn get(&self, usize) -> ::std::option::Option<&crate::ConservativeLayoutType> deref [crate::ConservativeLayoutType]; fn get_mut(&mut self, usize) -> ::std::option::Option<&mut crate::ConservativeLayoutType> deref [crate::ConservativeLayoutType]; fn push(&mut self, crate::ConservativeLayoutType); } } type crate::Scoped { #layout(size = 16, align = 8); fn new(&str) -> crate::Scoped; } type Box { #layout(size = 16, align = 8); } type crate::ZeroSizedType { #layout(size = 0, align = 1); wellknown_traits(Debug); fn new() -> crate::ZeroSizedType; fn method(&self); } mod crate { fn call_dyn_fn_multi_args(Box); } type crate::FieldTypeA { #layout(size = 12, align = 4); wellknown_traits(Debug, Copy); constructor { fizz: crate::FieldTypeC }; field fizz (offset = 0, type = crate::FieldTypeC ); } type crate::FieldTypeB { #heap_allocated; wellknown_traits(Debug, Copy); constructor { fizz: crate::FieldTypeC }; field fizz (offset = 0, type = crate::FieldTypeC ); } type crate::FieldTypeC { #layout(size = 12, align = 4); wellknown_traits(Debug, Copy); constructor { buzz_1: i32, buzz_2: i32, buzz_3: i32 }; field buzz_1 (offset = auto, type = i32 ); field buzz_2 (offset = auto, type = i32 ); field buzz_3 (offset = auto, type = i32 ); } type crate::TypeA { #layout(size = 28, align = 4); wellknown_traits(Debug, Copy); constructor { foo: i32, bar: crate::FieldTypeA, baz: crate::FieldTypeB }; field foo (offset = 0, type = i32 ); field bar (offset = 4, type = crate::FieldTypeA ); field baz (offset = 16, type = crate::FieldTypeB ); } type crate::TypeB { #heap_allocated; wellknown_traits(Debug, Copy); constructor { foo: i32, bar: crate::FieldTypeA, baz: crate::FieldTypeB }; field foo (offset = 0, type = i32 ); field bar (offset = 4, type = crate::FieldTypeA ); field baz (offset = 16, type = crate::FieldTypeB ); } type crate::ConservativeLayoutType { // bigger than the real size of 32 #layout_conservative(size = 48, align = 8 ); wellknown_traits(Debug); constructor { field1: f32, field2: i32, field3: ::std::string::String }; field field1 (offset = auto, type = f32 ); field field2 (offset = auto, type = i32 ); field field3 (offset = auto, type = ::std::string::String ); fn mem_size(&self) -> usize; fn mem_align(&self) -> usize; } ================================================ FILE: examples/regression_test1/src/lib.rs ================================================ #[rustfmt::skip] mod generated; #[allow(unused)] #[derive(Debug)] struct Foo { field1: i32, field2: String, } #[allow(unused)] #[derive(Debug, Copy, Clone)] struct FieldTypeA { pub fizz: FieldTypeC, } #[allow(unused)] #[derive(Debug, Copy, Clone)] // heap allocated struct FieldTypeB { pub fizz: FieldTypeC, } #[allow(unused)] #[derive(Debug, Copy, Clone)] // auto field offset struct FieldTypeC { pub buzz_1: i32, pub buzz_2: i32, pub buzz_3: i32, } #[allow(unused)] #[derive(Debug, Copy, Clone)] struct TypeA { pub foo: i32, pub bar: FieldTypeA, pub baz: FieldTypeB, } #[allow(unused)] #[derive(Debug, Copy, Clone)] // heap allocated struct TypeB { pub foo: i32, pub bar: FieldTypeA, pub baz: FieldTypeB, } #[allow(unused)] #[derive(Debug)] /// auto field offset + layout_conservative struct ConservativeLayoutType { pub field1: f32, pub field2: i32, pub field3: String, } #[allow(unused)] impl ConservativeLayoutType { pub fn mem_size(&self) -> usize { std::mem::size_of::() } pub fn mem_align(&self) -> usize { std::mem::align_of::() } } struct Scoped(&'static str); impl Scoped { fn new(message: &'static str) -> Self { println!("{message} -- started"); Self(message) } } impl Drop for Scoped { fn drop(&mut self) { println!("{} -- finished", self.0); println!(); } } fn call_dyn_fn_multi_args(func: Box) { let scope = Scoped::new("scope passed to dyn Fn"); func(2, scope, "hello"); println!("End of call_dyn_fn_multi_args"); } #[derive(Debug)] struct ZeroSizedType; impl ZeroSizedType { fn new() -> Self { Self } fn method(&self) { println!("Method call on ZST"); } } ================================================ FILE: examples/rustyline/.gitignore ================================================ generated.h generated.rs history.txt ================================================ FILE: examples/rustyline/Cargo.toml ================================================ [package] name = "example-rustyline" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rustyline = "12.0.0" ================================================ FILE: examples/rustyline/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_rustyline.a ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_rustyline ../../target/release/libexample_rustyline.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/rustyline/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_rustyline.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/rustyline/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib User32.lib EXAMPLE_NAME = rustyline GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/rustyline/README.md ================================================ # Example: Rustyline Line by line port of the [`rustyline` example](https://github.com/kkawakam/rustyline#example) in C++: ```Rust use rustyline::error::ReadlineError; use rustyline::{DefaultEditor, Result}; fn main() -> Result<()> { // `()` can be used when no completer is required let mut rl = DefaultEditor::new()?; #[cfg(feature = "with-file-history")] if rl.load_history("history.txt").is_err() { println!("No previous history."); } loop { let readline = rl.readline(">> "); match readline { Ok(line) => { rl.add_history_entry(line.as_str()); println!("Line: {}", line); }, Err(ReadlineError::Interrupted) => { println!("CTRL-C"); break }, Err(ReadlineError::Eof) => { println!("CTRL-D"); break }, Err(err) => { println!("Error: {:?}", err); break } } } #[cfg(feature = "with-file-history")] rl.save_history("history.txt"); Ok(()) } ``` To run this example: ``` make ./a.out ``` ================================================ FILE: examples/rustyline/expected_output.txt ================================================ No previous history. CTRL-D ================================================ FILE: examples/rustyline/main.cpp ================================================ #include #include #include #include #include "./generated.h" int main() { auto editor = rust::rustyline::DefaultEditor::new_().unwrap(); if (editor.load_history("history.txt"_rs).is_err()) { std::cout << "No previous history." << std::endl; } while (true) { auto r = editor.readline(">>> "_rs); if (r.is_err()) { auto e = r.unwrap_err(); if (e.matches_Eof()) { std::cout << "CTRL-D" << std::endl; } if (e.matches_Interrupted()) { std::cout << "CTRL-C" << std::endl; } break; } else { auto s = r.as_ref().unwrap().as_str(); std::string cpp_s((char *)s.as_ptr(), s.len()); std::cout << "Line: " << cpp_s << std::endl; editor.add_history_entry(s); } } editor.save_history("history.txt"_rs); } ================================================ FILE: examples/rustyline/main.zng ================================================ // This example uses various layout policies to demonstrate them. See https://hkalbasi.github.io/zngur/call_rust_from_cpp/layout_policy.html type str { wellknown_traits(?Sized); // Unsized types don't need layout policy fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; } type bool { #layout(size = 1, align = 1); // primitives like bool have stable layout wellknown_traits(Copy); } mod ::std { type string::String { #only_by_ref; // String has stable layout, but we don't use it by value, so we can use this policy. fn as_str(&self) -> &str; } } mod ::rustyline { type DefaultEditor { // DefaultEditor is a complex type defined by rustyline crate, so its layout may break // when upgrading the compiler or the rustyline itself. We can easily manage this kind // of breakage when we control the final binary build process, but when we don't control, it // can break. Using `#heap_allocate` we don't need to know the layout information at compile time. #heap_allocated; fn new() -> Result; fn readline(&mut self, &str) -> Result<::std::string::String>; fn load_history(&mut self, &str) -> Result<()>; fn add_history_entry<&str>(&mut self, &str) -> Result; fn save_history(&mut self, &str) -> Result<()>; } type error::ReadlineError { #heap_allocated; constructor Interrupted; constructor Eof; } type Result { #heap_allocated; fn unwrap(self) -> DefaultEditor; } type ::std::result::Result<&::std::string::String, &error::ReadlineError> { #heap_allocated; fn unwrap(self) -> &::std::string::String; } type Result<::std::string::String> { #heap_allocated; fn is_err(&self) -> bool; fn as_ref(&self) -> ::std::result::Result<&::std::string::String, &error::ReadlineError>; fn unwrap_err(self) -> error::ReadlineError; } type Result<()> { #heap_allocated; fn is_err(&self) -> bool; } type Result { #heap_allocated; fn is_err(&self) -> bool; } } ================================================ FILE: examples/rustyline/src/lib.rs ================================================ #[rustfmt::skip] mod generated; ================================================ FILE: examples/simple/.gitignore ================================================ generated.h generated.rs generated.cpp ================================================ FILE: examples/simple/Cargo.toml ================================================ [package] name = "example-simple" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/simple/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_simple.a ${CXX} -std=c++17 -Werror main.cpp generated.cpp -g -L ../../target/release/ -l example_simple ../../target/release/libexample_simple.a: cargo build --release generated.h generated.cpp ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/simple/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_simple.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/simple/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib EXAMPLE_NAME = simple GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp generated.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj generated.obj actual_output.txt 2>nul ================================================ FILE: examples/simple/README.md ================================================ # Example: Simple A simple example, used as a demo in the main README file. To run this example: ``` make ./a.out ``` ================================================ FILE: examples/simple/expected_output.txt ================================================ 17 s[2] = 7 thread panicked at examples/simple/src/generated.rs:370:40: called `Option::unwrap()` on a `None` value note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace s[4] = Rust panic happened hello 2 2 hello 5 7 hello 7 14 hello 3 17 34 17 vector iterator has been destructed [main.cpp:71] t = [ 10, 20, 60, ] ================================================ FILE: examples/simple/main.cpp ================================================ #include #include #include "./generated.h" // Rust values are available in the `::rust` namespace from their absolute path // in Rust template using Vec = rust::std::vec::Vec; template using Option = rust::std::option::Option; template using BoxDyn = rust::Box>; // You can implement Rust traits for your classes template class VectorIterator : public rust::std::iter::Iterator { std::vector vec; size_t pos; public: VectorIterator(std::vector &&v) : vec(v), pos(0) {} ~VectorIterator() { std::cout << "vector iterator has been destructed" << std::endl; } Option next() override { if (pos >= vec.size()) { return Option::None(); } T value = vec[pos++]; // You can construct Rust enum with fields in C++ return Option::Some(value); } }; int main() { // You can call Rust functions that return things by value, and store that // value in your stack. auto s = Vec::new_(); s.push(2); Vec::push(s, 5); s.push(7); Vec::push(s, 3); // You can call Rust functions just like normal Rust. std::cout << s.clone().into_iter().sum() << std::endl; // You can catch Rust panics as C++ exceptions try { std::cout << "s[2] = " << *s.get(2).unwrap() << std::endl; std::cout << "s[4] = " << *s.get(4).unwrap() << std::endl; } catch (rust::Panic e) { std::cout << "Rust panic happened" << std::endl; } int state = 0; // You can convert a C++ lambda into a `Box` and friends. auto f = BoxDyn>::make_box([&](int32_t x) { state += x; std::cout << "hello " << x << " " << state << "\n"; return x * 2; }); // And pass it to Rust functions that accept closures. auto x = s.into_iter().map(std::move(f)).sum(); std::cout << x << " " << state << "\n"; std::vector vec{10, 20, 60}; // You can convert a C++ type that implements `Trait` to a `Box`. // `make_box` is similar to the `make_unique`, it takes constructor arguments // and construct it inside the `Box` (instead of `unique_ptr`). auto vec_as_iter = BoxDyn>::make_box< VectorIterator>(std::move(vec)); // Then use it like a normal Rust value. auto t = vec_as_iter.collect(); // Some utilities are also provided. For example, `zngur_dbg` is the // equivalent of `dbg!` macro. zngur_dbg(t); } ================================================ FILE: examples/simple/main.zng ================================================ #convert_panic_to_exception type Box i32> { #layout(size = 16, align = 8); } mod ::std { type option::Option { #layout(size = 8, align = 4); wellknown_traits(Copy); constructor None; constructor Some(i32); fn unwrap(self) -> i32; } type option::Option<&i32> { #layout(size = 8, align = 8); wellknown_traits(Copy); fn unwrap(self) -> &i32; } type iter::Map<::std::vec::IntoIter, Box i32>> { #layout(size = 48, align = 8); fn sum(self) -> i32; } mod vec { type IntoIter { #layout(size = 32, align = 8); fn sum(self) -> i32; fn map i32>>(self, Box i32>) -> ::std::iter::Map<::std::vec::IntoIter, Box i32>>; } type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec; fn push(&mut self, i32); fn clone(&self) -> Vec; fn get(&self, usize) -> ::std::option::Option<&i32> deref [i32]; fn into_iter(self) -> ::std::vec::IntoIter; } } trait iter::Iterator:: { fn next(&mut self) -> ::std::option::Option; } } type Box> { #layout(size = 16, align = 8); fn collect<::std::vec::Vec>(self) -> ::std::vec::Vec; } ================================================ FILE: examples/simple/src/lib.rs ================================================ #[rustfmt::skip] mod generated; ================================================ FILE: examples/simple_import/.gitignore ================================================ generated.h generated.rs generated.cpp ================================================ FILE: examples/simple_import/Cargo.toml ================================================ [package] name = "example-simple-import" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/simple_import/Makefile ================================================ .PHONY: ../../target/release/libexample_simple_import.a a.out: main.cpp foo.cpp bar.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_simple_import.a ${CXX} -std=c++11 main.cpp foo.cpp bar.cpp -g -L ../../target/release/ -l example_simple_import ../../target/release/libexample_simple_import.a: cargo build --release generated.h ./src/generated.rs: main.zng primitives.zng foo.zng bar.zng cd ../../zngur-cli && cargo run g -i ../examples/simple_import/main.zng --crate-name "crate" clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/simple_import/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib EXAMPLE_NAME = simple_import GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp foo.cpp bar.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj foo.obj bar.obj actual_output.txt 2>nul ================================================ FILE: examples/simple_import/README.md ================================================ # Example: Import and Merge A demonstration of Zngur's `import` and `merge` functionality using four focused modules. ## Structure - **`primitives.zng`** - Defines basic primitive types (`bool`) - **`foo.{zng,cpp}`** - Imports primitives, defines `Vec` APIs and returns a populated Vec - **`bar.{zng,cpp}`** - Imports primitives, defines `Option` APIs and returns an Option - **`main.{zng,cpp}`** - Imports foo and bar (transitively gets primitives), extends both types with additional APIs, and demonstrates everything ## API Extensions in main.zng The main module doesn't just import - it extends the imported types: - **Vec**: Adds `is_empty()` and `clear()` methods beyond `foo.zng`'s `new()` and `push()` - **Option**: Adds `unwrap()` method beyond `bar.zng`'s constructors and `is_some()`/`is_none()` ## Running ```bash make ./a.out ``` ================================================ FILE: examples/simple_import/bar.cpp ================================================ #include "generated.h" // Creates and returns a Rust Option with Some(String) rust::std::option::Option bar() { // Create Some(String) return rust::std::option::Option::Some( rust::std::string::String::new_() ); } ================================================ FILE: examples/simple_import/bar.zng ================================================ // Bar module - exports Option APIs for C++ usage merge "./primitives.zng"; mod ::std { mod string { type String { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> String; } } type option::Option<::std::string::String> { #layout(size = 24, align = 8); constructor None; constructor Some(::std::string::String); fn is_some(&self) -> bool; fn is_none(&self) -> bool; } } ================================================ FILE: examples/simple_import/expected_output.txt ================================================ foo(): Creating a Rust Vec Vec contents: [main.cpp:13] numbers = [ 10, 20, 30, ] Vec is_empty(): 0 After clear(), is_empty(): 1 bar(): Creating Rust Option some_value.is_some(): 1 none_value.is_none(): 1 Unwrapped string: [main.cpp:33] unwrapped_string = "" ================================================ FILE: examples/simple_import/foo.cpp ================================================ #include "generated.h" // Creates and returns a Rust Vec with sample data rust::std::vec::Vec foo() { // Create a new Rust Vec auto numbers = rust::std::vec::Vec::new_(); // Add some numbers numbers.push(10); numbers.push(20); numbers.push(30); return numbers; } ================================================ FILE: examples/simple_import/foo.zng ================================================ // Foo module - exports Vec APIs for C++ usage merge "./primitives.zng"; mod ::std { mod vec { type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); fn new() -> Vec; fn push(&mut self, i32); } } } ================================================ FILE: examples/simple_import/main.cpp ================================================ #include #include "generated.h" // External function declarations from foo.cpp and bar.cpp extern rust::std::vec::Vec foo(); extern rust::std::option::Option bar(); int main() { // Get Vec from foo() and demonstrate both imported and extended APIs std::cout << "foo(): Creating a Rust Vec" << std::endl; auto numbers = foo(); std::cout << " Vec contents: "; zngur_dbg(numbers); // Use extended APIs defined in main.zng std::cout << " Vec is_empty(): " << numbers.is_empty() << std::endl; numbers.clear(); // Clear the vector using main.zng API std::cout << " After clear(), is_empty(): " << numbers.is_empty() << std::endl; std::cout << std::endl; // Get Option from bar() and demonstrate both imported and extended APIs std::cout << "bar(): Creating Rust Option" << std::endl; auto some_value = bar(); auto none_value = rust::std::option::Option::None(); // Use imported APIs from bar.zng std::cout << " some_value.is_some(): " << some_value.is_some() << std::endl; std::cout << " none_value.is_none(): " << none_value.is_none() << std::endl; // Use extended API defined in main.zng auto unwrapped_string = some_value.unwrap(); // Use main.zng API std::cout << " Unwrapped string: "; zngur_dbg(unwrapped_string); return 0; } ================================================ FILE: examples/simple_import/main.zng ================================================ // Main module - imports foo and bar modules to demonstrate import/merge merge "./foo.zng"; merge "./bar.zng"; // Note: All APIs from foo.zng and bar.zng are now available // Primitives from primitives.zng are also available transitively // The std module definitions are automatically merged // Main module extensions - adding additional APIs to imported types mod ::std { mod vec { type Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); // Inherited methods from foo.zng (automatically merged) fn new() -> Vec; fn push(&mut self, i32); // Additional methods beyond what foo.zng provides fn is_empty(&self) -> bool; fn clear(&mut self); } } type option::Option<::std::string::String> { #layout(size = 24, align = 8); // Inherited constructors and methods from bar.zng (automatically merged) constructor None; constructor Some(::std::string::String); fn is_some(&self) -> bool; fn is_none(&self) -> bool; // Additional methods beyond what bar.zng provides fn unwrap(self) -> ::std::string::String; } } ================================================ FILE: examples/simple_import/primitives.zng ================================================ // Primitive types used across the import/merge example type bool { #layout(size = 1, align = 1); wellknown_traits(Copy); } ================================================ FILE: examples/simple_import/src/lib.rs ================================================ // Minimal lib.rs - just includes the generated Zngur bindings #[rustfmt::skip] mod generated; ================================================ FILE: examples/tutorial/.gitignore ================================================ generated.h generated.rs ================================================ FILE: examples/tutorial/Cargo.toml ================================================ [package] name = "example-tutorial" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/tutorial/Makefile ================================================ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_tutorial.a ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_tutorial ../../target/release/libexample_tutorial.a: cargo build --release generated.h ./src/generated.rs: main.zng cd ../../zngur-cli && cargo run g -i ../examples/tutorial/main.zng --crate-name "crate" .PHONY: ../../target/release/libexample_tutorial.a generated.h clean clean: rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt ================================================ FILE: examples/tutorial/NMakefile ================================================ CXX = cl.exe CXXFLAGS = /W4 /DEBUG /EHsc /std:c++20 WINLIBS = ntdll.lib EXAMPLE_NAME = tutorial GENERATED = generated.h src/generated.rs RUSTLIB_PATH = ../../target/release/ RUSTLIB = example_$(EXAMPLE_NAME).lib a.exe : main.cpp src/lib.rs $(GENERATED) $(RUSTLIB_PATH)/$(RUSTLIB) $(CXX) $(CXXFLAGS) main.cpp /Fe:a.exe /link $(WINLIBS) $(RUSTLIB) /LIBPATH:$(RUSTLIB_PATH) $(RUSTLIB_PATH)/$(RUSTLIB) : cargo build --release $(GENERATED) : main.zng cd ../../zngur-cli && cargo run g -i ../examples/$(EXAMPLE_NAME)/main.zng --crate-name "crate" clean : - del /f /q generated.h generated.cpp src\generated.rs a.exe main.obj actual_output.txt 2>nul ================================================ FILE: examples/tutorial/README.md ================================================ # Example: Tutorial Full code of the [Tutorial](https://hkalbasi.github.io/zngur/tutorial.html) part 1 (Calling Rust from C++) in the Zngur book. To run this example: ``` make ./a.out ``` ================================================ FILE: examples/tutorial/expected_output.txt ================================================ [main.cpp:7] inventory = Inventory { items: [ Item { name: "banana", size: 7, }, Item { name: "banana", size: 7, }, Item { name: "banana", size: 7, }, Item { name: "apple", size: 5, }, ], remaining_space: 974, } [main.cpp:10] v = [ Item { name: "banana", size: 7, }, Item { name: "banana", size: 7, }, Item { name: "banana", size: 7, }, Item { name: "apple", size: 5, }, ] ================================================ FILE: examples/tutorial/main.cpp ================================================ #include "./generated.h" int main() { auto inventory = rust::crate::Inventory::new_empty(1000); inventory.add_banana(3); inventory.add_item(rust::crate::Item("apple"_rs.to_owned(), 5)); zngur_dbg(inventory); rust::std::vec::Vec v = inventory.into_items(); zngur_dbg(v); } ================================================ FILE: examples/tutorial/main.zng ================================================ type ::std::vec::Vec { #layout(size = 24, align = 8); wellknown_traits(Debug); } type str { wellknown_traits(?Sized); fn to_owned(&self) -> ::std::string::String; } type ::std::string::String { #layout(size = 24, align = 8); } type crate::Item { #layout(size = 32, align = 8); constructor { name: ::std::string::String, size: u32 }; } type crate::Inventory { #layout(size = 32, align = 8); wellknown_traits(Debug); fn new_empty(u32) -> crate::Inventory; fn add_banana(&mut self, u32); fn add_item(&mut self, crate::Item); fn into_items(self) -> ::std::vec::Vec; } ================================================ FILE: examples/tutorial/src/lib.rs ================================================ #![allow(dead_code)] #[rustfmt::skip] mod generated; #[derive(Debug)] struct Item { name: String, size: u32, } #[derive(Debug)] struct Inventory { items: Vec, remaining_space: u32, } impl Inventory { fn new_empty(space: u32) -> Self { Self { items: vec![], remaining_space: space, } } fn add_item(&mut self, item: Item) { self.remaining_space -= item.size; self.items.push(item); } fn add_banana(&mut self, count: u32) { for _ in 0..count { self.add_item(Item { name: "banana".to_owned(), size: 7, }); } } fn into_items(self) -> Vec { self.items } } ================================================ FILE: examples/tutorial-wasm32/.gitignore ================================================ generated.h generated.rs generated.cpp generated.o main.js target/ a.out example_tutorial_wasm32.wasm # WASI SDK and dependencies wasi-sdk-* wasi-sdk-latest.tar.gz wasi-sdk-installed wasm32-wasip1-target # Build artifacts actual_output.txt *.wasm ================================================ FILE: examples/tutorial-wasm32/Cargo.toml ================================================ [package] name = "example-tutorial-wasm32" version = "0.6.0" edition = "2024" rust-version = "1.85" license = "MIT OR Apache-2.0" publish = false [lib] crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ================================================ FILE: examples/tutorial-wasm32/Makefile ================================================ # WASI build flags for C++ compilation with wasmtime WASIFLAGS = -std=c++14 \ --target=wasm32-wasi \ --sysroot=$(WASI_SDK_PATH)/share/wasi-sysroot \ -D_WASI_EMULATED_SIGNAL \ -lwasi-emulated-signal \ -fno-exceptions # Use the mise-installed WASI SDK C++ compiler # Load mise environment if WASI_SDK_PATH is not set ifeq ($(WASI_SDK_PATH),) $(eval $(shell mise env)) endif CXX = $(WASI_SDK_PATH)/bin/clang++ # Default target all: run a.out # Run the example with wasmtime (installs all deps automatically) run: main.wasm wasmtime run --allow-precompiled main.wasm # CI expects a.out; we create it as a bash script that simply execs wasmtime a.out: main.wasm @printf '%s\n' '#!/usr/bin/env bash' 'exec wasmtime run --allow-precompiled "$$(dirname "$$0")/main.wasm" "$$@"' > $@ @chmod +x $@ main.wasm: main.cpp generated.h generated.cpp \ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a $(CXX) $(WASIFLAGS) \ main.cpp generated.cpp \ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a \ -o $@ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a: wasm32-wasip1-target generated.h ./src/generated.rs ./src/lib.rs cargo build --target=wasm32-wasip1 --release generated.h ./src/generated.rs generated.cpp: main32.zng cargo run --release --manifest-path ../../zngur-cli/Cargo.toml g -i main32.zng # Ensure wasm32-wasip1 target is installed wasm32-wasip1-target: @rustup target list --installed | grep -q wasm32-wasip1 || { \ echo "Installing wasm32-wasip1 Rust target..."; \ rustup target add wasm32-wasip1; \ } @touch wasm32-wasip1-target clean: cargo clean rm -f generated.h ./src/generated.rs generated.cpp generated.o rm -f main.wasm example_tutorial_wasm32.wasm a.out rm -f wasm32-wasip1-target FORCE: ; ================================================ FILE: examples/tutorial-wasm32/NMakefile ================================================ # can not eval mise after the fact in nmake, must preeval WASIFLAGS = -std=c++17 \ --target=wasm32-wasi \ --sysroot=$(WASI_SDK_PATH)/share/wasi-sysroot \ -D_WASI_EMULATED_SIGNAL \ -lwasi-emulated-signal \ -fno-exceptions CXX = $(WASI_SDK_PATH)\bin\clang++ # Default target all: run a.bat # Run the example with wasmtime (installs all deps automatically) run: main.wasm wasmtime run --allow-precompiled main.wasm # CI expects a.bat; we create it as a batch script that simply execs wasmtime a.bat: main.wasm echo @echo off > $@ echo wasmtime run --allow-precompiled "%0\..\main.wasm" "$$@" >> $@ main.wasm: main.cpp generated.h generated.cpp \ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a $(CXX) $(WASIFLAGS) \ main.cpp generated.cpp \ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a \ -o $@ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a: generated.h ./src/generated.rs ./src/lib.rs cargo build --target=wasm32-wasip1 --release generated.h ./src/generated.rs generated.cpp: main32.zng cargo run --release --manifest-path ../../zngur-cli/Cargo.toml g -i main32.zng clean: cargo clean - del /f generated.h .\src\generated.rs generated.cpp generated.o 2>nul - del /f main.wasm example_tutorial_wasm32.wasm a.bat 2>nul ================================================ FILE: examples/tutorial-wasm32/README.md ================================================ # Wasm32 Example This example builds a sample application for wasmtime to test zngur's support for basic WASM applications. ## To build and run ``` $ make run ``` This automatically installs all dependencies (wasmtime, WASI SDK, Rust targets) and runs the example. ## Alternative targets ``` $ make # Same as 'make run' $ make main.wasm # Build without running $ make a.out # Create executable wrapper script $ make clean # Clean all build artifacts ``` ================================================ FILE: examples/tutorial-wasm32/expected_output.txt ================================================ 17 s[2] = 7 Rust panic would happen if we accessed invalid index, but we avoid it hello 2 2 hello 5 7 hello 7 14 hello 3 17 34 17 vector iterator has been destructed [main.cpp:70] t = [ 10, 20, 60, ] ================================================ FILE: examples/tutorial-wasm32/main.cpp ================================================ #include #include #include "./generated.h" // Rust values are available in the `::rust` namespace from their absolute path // in Rust template using Vec = rust::std::vec::Vec; template using Option = rust::std::option::Option; template using BoxDyn = rust::Box>; // You can implement Rust traits for your classes template class VectorIterator : public rust::std::iter::Iterator { std::vector vec; size_t pos; public: VectorIterator(std::vector &&v) : vec(v), pos(0) {} ~VectorIterator() { std::cout << "vector iterator has been destructed" << std::endl; } Option next() override { if (pos >= vec.size()) { return Option::None(); } T value = vec[pos++]; // You can construct Rust enum with fields in C++ return Option::Some(value); } }; int main() { // You can call Rust functions that return things by value, and store that // value in your stack. auto s = Vec::new_(); s.push(2); Vec::push(s, 5); s.push(7); Vec::push(s, 3); // You can call Rust functions just like normal Rust. std::cout << s.clone().into_iter().sum() << std::endl; // Access valid indices std::cout << "s[2] = " << *s.get(2).unwrap() << std::endl; // TODO: Uncomment this line after enabling wasmtime exceptions. // std::cout << "s[4] = " << *s.get(4).unwrap() << std::endl; std::cout << "Rust panic would happen if we accessed invalid index, but we avoid it" << std::endl; int state = 0; // You can convert a C++ lambda into a `Box` and friends. auto f = BoxDyn>::make_box([&](int32_t x) { state += x; std::cout << "hello " << x << " " << state << "\n"; return x * 2; }); // And pass it to Rust functions that accept closures. auto x = s.into_iter().map(std::move(f)).sum(); std::cout << x << " " << state << "\n"; std::vector vec{10, 20, 60}; // You can convert a C++ type that implements `Trait` to a `Box`. // `make_box` is similar to the `make_unique`, it takes constructor arguments // and construct it inside the `Box` (instead of `unique_ptr`). auto vec_as_iter = BoxDyn>::make_box< VectorIterator>(std::move(vec)); // Then use it like a normal Rust value. auto t = vec_as_iter.collect(); // Some utilities are also provided. For example, `zngur_dbg` is the // equivalent of `dbg!` macro. zngur_dbg(t); return 0; } ================================================ FILE: examples/tutorial-wasm32/main32.zng ================================================ type Box i32> { #layout(size = 8, align = 4); } mod ::std { type option::Option { #layout(size = 8, align = 4); wellknown_traits(Copy); constructor None; constructor Some(i32); fn unwrap(self) -> i32; } type option::Option<&i32> { #layout(size = 4, align = 4); wellknown_traits(Copy); fn unwrap(self) -> &i32; } type iter::Map<::std::vec::IntoIter, Box i32>> { #layout(size = 24, align = 4); fn sum(self) -> i32; } mod vec { type IntoIter { #layout(size = 16, align = 4); fn sum(self) -> i32; fn map i32>>(self, Box i32>) -> ::std::iter::Map<::std::vec::IntoIter, Box i32>>; } type Vec { #layout(size = 12, align = 4); wellknown_traits(Debug); fn new() -> Vec; fn push(&mut self, i32); fn clone(&self) -> Vec; fn get(&self, usize) -> ::std::option::Option<&i32> deref [i32]; fn into_iter(self) -> ::std::vec::IntoIter; } } trait iter::Iterator:: { fn next(&mut self) -> ::std::option::Option; } } type Box> { #layout(size = 8, align = 4); fn collect<::std::vec::Vec>(self) -> ::std::vec::Vec; } ================================================ FILE: examples/tutorial-wasm32/src/lib.rs ================================================ #![allow(dead_code)] #[rustfmt::skip] mod generated; pub fn add_one(x: usize) -> usize { x + 1 } #[derive(Debug)] struct Item { name: String, size: u32, } #[derive(Debug)] struct Inventory { items: Vec, remaining_space: u32, } impl Inventory { fn new_empty(space: u32) -> Self { Self { items: vec![], remaining_space: space, } } fn add_item(&mut self, item: Item) { self.remaining_space -= item.size; self.items.push(item); } fn add_banana(&mut self, count: u32) { for _ in 0..count { self.add_item(Item { name: "banana".to_owned(), size: 7, }); } } fn into_items(self) -> Vec { self.items } } ================================================ FILE: examples/tutorial_cpp/Cargo.toml ================================================ [package] name = "example-tutorial_cpp" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [build-dependencies] cc = "1.0" build-rs = "0.1.2" zngur = { path = "../../zngur" } ================================================ FILE: examples/tutorial_cpp/README.md ================================================ # Example: Tutorial Cpp Full code of the [Tutorial](https://hkalbasi.github.io/zngur/tutorial.html) part 2 (Calling C++ from Rust) in the Zngur book. To run this example: ``` cargo run ``` ================================================ FILE: examples/tutorial_cpp/build.rs ================================================ #[cfg(not(target_os = "windows"))] use std::env; use zngur::Zngur; fn main() { build::rerun_if_changed("main.zng"); build::rerun_if_changed("impls.cpp"); build::rerun_if_env_changed("CXX"); #[cfg(not(target_os = "windows"))] let cxx = env::var("CXX").unwrap_or("c++".to_owned()); let crate_dir = build::cargo_manifest_dir(); let out_dir = build::out_dir(); // Force rerun if generated files don't exist let generated_files = [ out_dir.join("generated.cpp"), out_dir.join("generated.h"), out_dir.join("generated.rs"), ]; for file in &generated_files { if !file.exists() { println!("cargo:rerun-if-changed=nonexistent_trigger_file"); break; } } Zngur::from_zng_file(crate_dir.join("main.zng")) .with_cpp_file(out_dir.join("generated.cpp")) .with_h_file(out_dir.join("generated.h")) .with_rs_file(out_dir.join("generated.rs")) .with_crate_name("crate") .with_zng_header_in_place() .generate(); let my_build = &mut cc::Build::new(); let my_build = my_build.cpp(true).std("c++20"); #[cfg(not(target_os = "windows"))] my_build.compiler(&cxx); my_build.include(&crate_dir).include(&out_dir); let my_build = || my_build.clone(); my_build() .file(out_dir.join("generated.cpp")) .compile("zngur_generated"); my_build().file("impls.cpp").compile("impls"); } ================================================ FILE: examples/tutorial_cpp/expected_output.txt ================================================ [examples/tutorial_cpp/src/main.rs:12:5] inventory = Inventory { remaining_space: 974, items: [Item { name: "banana", size: 7 }, Item { name: "banana", size: 7 }, Item { name: "banana", size: 7 }, Item { name: "apple", size: 5 }] } ================================================ FILE: examples/tutorial_cpp/impls.cpp ================================================ #include "generated.h" #include using namespace rust::crate; template using Ref = rust::Ref; template using RefMut = rust::RefMut; rust::Ref rust_str_from_c_str(const char* input) { return rust::std::ffi::CStr::from_ptr(reinterpret_cast(input)).to_str().expect("invalid_utf8"_rs); } Inventory rust::Impl::new_empty(uint32_t space) { return Inventory( rust::ZngurCppOpaqueOwnedObject::build(space)); } rust::Unit rust::Impl::add_banana(RefMut self, uint32_t count) { self.cpp().add_banana(count); return {}; } rust::Unit rust::Impl::add_item(RefMut self, Item item) { self.cpp().add_item(item.cpp()); return {}; } Item rust::Impl::new_(Ref name, uint32_t size) { return Item(rust::ZngurCppOpaqueOwnedObject::build( cpp_inventory::Item{ .name = ::std::string(reinterpret_cast(name.as_ptr()), name.len()), .size = size})); } rust::std::fmt::Result rust::Impl::fmt( Ref self, RefMut f) { ::std::string result = "Inventory { remaining_space: "; result += ::std::to_string(self.cpp().remaining_space); result += ", items: ["; bool is_first = true; for (const auto &item : self.cpp().items) { if (!is_first) { result += ", "; } else { is_first = false; } result += "Item { name: \""; result += item.name; result += "\", size: "; result += ::std::to_string(item.size); result += " }"; } result += "] }"; return f.write_str(rust_str_from_c_str(result.c_str())); } ================================================ FILE: examples/tutorial_cpp/inventory.h ================================================ #include #include #include namespace cpp_inventory { struct Item { std::string name; uint32_t size; }; struct Inventory { std::vector items; uint32_t remaining_space; Inventory(uint32_t space) : items(), remaining_space(space) {} void add_item(Item item) { remaining_space -= item.size; items.push_back(std::move(item)); } void add_banana(uint32_t count) { for (uint32_t i = 0; i < count; i += 1) { add_item(Item{ .name = "banana", .size = 7, }); } } }; } // namespace cpp_inventory ================================================ FILE: examples/tutorial_cpp/main.zng ================================================ #cpp_additional_includes " #include " type str { wellknown_traits(?Sized); fn as_ptr(&self) -> *const u8; fn len(&self) -> usize; } type ::std::ffi::CStr { wellknown_traits(?Sized); fn from_ptr(*const i8) -> &::std::ffi::CStr; fn to_str(&self) -> ::std::result::Result<&str, ::std::str::Utf8Error>; } type ::std::result::Result<&str, ::std::str::Utf8Error> { #layout(size = 24, align = 8); fn expect(self, &str) -> &str; } type crate::Inventory { #layout(size = 16, align = 8); constructor(ZngurCppOpaqueOwnedObject); #cpp_value "0" "::cpp_inventory::Inventory"; } type crate::Item { #layout(size = 16, align = 8); constructor(ZngurCppOpaqueOwnedObject); #cpp_value "0" "::cpp_inventory::Item"; } type ::std::fmt::Result { #layout(size = 1, align = 1); constructor Ok(()); } type ::std::fmt::Formatter { #only_by_ref; fn write_str(&mut self, &str) -> ::std::fmt::Result; } extern "C++" { impl crate::Inventory { fn new_empty(u32) -> crate::Inventory; fn add_banana(&mut self, u32); fn add_item(&mut self, crate::Item); } impl crate::Item { fn new(&str, u32) -> crate::Item; } impl std::fmt::Debug for crate::Inventory { fn fmt(&self, &mut ::std::fmt::Formatter) -> ::std::fmt::Result; } } ================================================ FILE: examples/tutorial_cpp/src/main.rs ================================================ mod generated { include!(concat!(env!("OUT_DIR"), "/generated.rs")); } struct Inventory(generated::ZngurCppOpaqueOwnedObject); struct Item(generated::ZngurCppOpaqueOwnedObject); fn main() { let mut inventory = Inventory::new_empty(1000); inventory.add_banana(3); inventory.add_item(Item::new("apple", 5)); dbg!(inventory); } ================================================ FILE: mise.toml ================================================ [tools] cspell = "9.4.0" dprint = "0.50.2" wasmtime = "36.0.2" [tools."github:WebAssembly/wasi-sdk"] version = "30" version_prefix = "wasi-sdk-" # don't export clang binaries filter_bins = "" [tools."http:emsdk"] version = "5.0.2" url = "https://github.com/emscripten-core/emsdk/archive/refs/tags/{{version}}.tar.gz" [env] WASI_SDK_PATH = { value = '{{ tools["github:WebAssembly/wasi-sdk"].path }}', tools = true } EMSDK_PATH = { value = '{{ [tools["http:emsdk"].path] | concat(with= ["emsdk-", tools["http:emsdk"].version] | join ) | join_path }}', tools = true } RUST_BACKTRACE = { value = "0" } ================================================ FILE: xtask/Cargo.toml ================================================ [package] name = "xtask" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] xshell = "0.2.7" anyhow = "1.0" clap = { version = "4.3.12", features = ["derive"] } ================================================ FILE: xtask/src/ci.rs ================================================ use crate::format_book; use anyhow::{Context, Result}; use xshell::{Shell, cmd}; fn check_crate(sh: &Shell) -> Result<()> { cmd!(sh, "cargo check").run()?; cmd!(sh, "cargo fmt --check") .run() .with_context(|| "Crate is not formatted. Run `cargo fmt`")?; Ok(()) } fn check_book_formatting() -> Result<()> { format_book::main(false /* don't fix */) .with_context(|| "Book markdown files are not formatted. Run `cargo xtask format-book`") } fn check_examples(sh: &Shell, fix: bool) -> Result<()> { const CARGO_PROJECTS: &[&str] = &["cxx_demo", "tutorial_cpp"]; let examples_dir = sh.current_dir().join("examples"); sh.change_dir("examples"); let examples = sh.read_dir(".")?; for example in examples { let mut skip = false; let example = example .file_name() .unwrap_or_default() .to_str() .ok_or(anyhow::anyhow!("Non utf8 example name?"))?; println!("Building and testing {example}"); sh.change_dir(example); println!("Working in {}", sh.current_dir().display()); if CARGO_PROJECTS.contains(&example) { #[cfg(not(target_os = "windows"))] { // Clean generated files for Cargo projects let _ = cmd!( sh, "rm -f generated.h generated.cpp src/generated.rs actual_output.txt" ) .run(); cmd!(sh, "cargo build") .run() .with_context(|| format!("Building example `{example}` failed"))?; let bash_cmd = format!( "../../target/debug/example-{example} 2>&1 | sed -e s/thread.*\\(.*\\)/thread/g > actual_output.txt" ); cmd!(sh, "bash -c {bash_cmd}") .run() .with_context(|| format!("Running example `{example}` failed"))?; } #[cfg(target_os = "windows")] { // Clean generated files for Cargo projects let _ = cmd!( sh, "cmd /c 'del /f /q generated.h generated.cpp src\\generated.rs actual_output.txt 2>nul'" ) .run(); cmd!(sh, "cmd /c 'cargo build'") .run() .with_context(|| format!("Building example `{example}` failed"))?; let batch_cmd = format!("..\\..\\target\\debug\\example-{example} > actual_output.txt 2>&1"); cmd!(sh, "cmd /c {batch_cmd}") .run() .with_context(|| format!("Running example `{example}` failed"))?; cmd!(sh, "pwsh -Command '(Get-Content actual_output.txt) -replace \"thread.*\\(.*\\)\", \"thread\" -replace \"\\\\\", \"/\"| Out-File actual_output.txt'") .run() .with_context(|| format!("Filtering example `{example}` thread output failed"))?; } } else { #[cfg(not(target_os = "windows"))] if sh.path_exists("Makefile") { // Clean generated files for Make projects cmd!(sh, "make clean") .run() .with_context(|| format!("Cleaning example `{example}` failed"))?; cmd!(sh, "make") .run() .with_context(|| format!("Building example `{example}` failed"))?; cmd!( sh, "bash -c './a.out 2>&1 | sed -e s/thread.*\\(.*\\)/thread/g > actual_output.txt'" ) .run() .with_context(|| format!("Running example `{example}` failed"))?; if sh.path_exists("b.out") { cmd!( sh, "bash -c './b.out 2>&1 | sed -r s/thread.*\\(.*\\)/thread/g >> actual_output.txt'" ) .run() .with_context(|| format!("Running example `{example}` b.out failed"))?; } } else { skip = true; println!("Skipping {example}, no Makefile for this example"); } #[cfg(target_os = "windows")] if sh.path_exists("NMakefile") { // Clean generated files for NMake projects cmd!(sh, "nmake /f NMakefile clean") .run() .with_context(|| format!("Cleaning example `{example}` failed"))?; cmd!(sh, "nmake /f NMakefile") .run() .with_context(|| format!("Building example `{example}` failed"))?; if sh.path_exists("a.bat") { cmd!(sh, "cmd /c '.\\a.bat > actual_output.txt 2>&1'") .run() .with_context(|| format!("Running example `{example}` failed"))?; } else { cmd!(sh, "cmd /c '.\\a.exe > actual_output.txt 2>&1'") .run() .with_context(|| format!("Running example `{example}` failed"))?; } if sh.path_exists("b.exe") { cmd!(sh, "cmd /c '.\\b.exe >> actual_output.txt 2>&1'") .run() .with_context(|| format!("Running example `{example}` b.exe failed"))?; } cmd!(sh, "pwsh -Command '(Get-Content actual_output.txt) -replace \"thread.*\\(.*\\)\", \"thread\" -replace \"\\\\\", \"/\"| Out-File actual_output.txt'") .run() .with_context(|| format!("Filtering example `{example}` thread output failed"))?; } else { skip = true; println!("Skipping {example}, no NMakefile for this example"); } } if fix { sh.copy_file("./actual_output.txt", "./expected_output.txt")?; } if !skip { #[cfg(not(target_os = "windows"))] cmd!(sh, "diff actual_output.txt expected_output.txt") .run() .with_context(|| format!("Example `{example}` output differs from expected."))?; #[cfg(target_os = "windows")] cmd!(sh, "cmd /c 'fc actual_output.txt expected_output.txt'") .run() .with_context(|| format!("Example `{example}` output differs from expected."))?; cmd!(sh, "cargo fmt --check").run().with_context(|| { format!("Example `{example}` is not formatted. Run `cargo fmt`") })?; } sh.change_dir(&examples_dir); } Ok(()) } pub fn main(fix: bool) -> Result<()> { let sh = &Shell::new()?; println!("Cargo version = {}", cmd!(sh, "cargo --version").read()?); #[cfg(not(target_os = "windows"))] { let cxx = sh.var("CXX")?; println!("CXX version = {}", cmd!(sh, "{cxx} --version").read()?); } #[cfg(target_os = "windows")] let msvc_ver: u32 = { let msvc = String::from_utf8(cmd!(sh, "cl.exe /help").output()?.stderr)?; let msvc = msvc .lines() .next() .and_then(|line| line.split("Version ").nth(1)) .unwrap_or_default(); let mut parts = msvc.split("."); let major = parts.next().unwrap_or("0"); let minor = parts.next().unwrap_or("0"); let msvc_ver: u32 = format!("{major}{minor}").parse()?; println!("MSVC version = {msvc} ({msvc_ver})",); msvc_ver }; #[cfg(not(target_os = "windows"))] sh.set_var("RUSTFLAGS", "-D warnings"); #[cfg(target_os = "windows")] { // Prevent link.exe error:1318 let link_debug = if msvc_ver > 1944 { // sorry, msvc 2026 removed FASTLINK support so it can't prevent pdb // corruption. best we can do is disable debug symbols "/DEBUG:NONE" } else { "/DEBUG:FASTLINK" }; sh.set_var( "RUSTFLAGS", format!("-D warnings -C link-arg={link_debug} -C link-arg=/INCREMENTAL:NO"), ); } if fix { cmd!(sh, "cargo fmt --all").run()?; if let Err(e) = format_book::main(true /* fix */) { eprintln!("Warning: Failed to format book: {}", e); } } #[cfg(not(target_os = "windows"))] cmd!(sh, "cspell .") .run() .with_context(|| "Failed to check word spellings")?; #[cfg(target_os = "windows")] cmd!(sh, "cspell.cmd .") .run() .with_context(|| "Failed to check word spellings")?; // Check book formatting check_book_formatting().with_context(|| "Book formatting check failed")?; for dir in sh.read_dir(".")? { if sh.path_exists(dir.join("Cargo.toml")) { let _crate_dir = sh.push_dir(dir.file_name().unwrap_or_default()); check_crate(sh).with_context(|| format!("Checking crate {dir:?} failed"))?; } } check_examples(sh, fix).with_context(|| "Checking examples failed")?; // it's nigh impossible to get this to run under MSVC #[cfg(not(target_os = "windows"))] cmd!(sh, "cargo test --all-features").run()?; Ok(()) } ================================================ FILE: xtask/src/format_book.rs ================================================ use anyhow::{Context, Result, bail}; use xshell::{Shell, cmd}; pub fn main(fix: bool) -> Result<()> { let sh = Shell::new()?; // Check if dprint config exists if !sh.path_exists("dprint.json") { bail!("dprint.json config file not found. Please create it first."); } // Check if book source directory exists if !sh.path_exists("book/src") { bail!("Book source directory 'book/src' not found"); } if fix { println!("Formatting markdown files..."); cmd!(sh, "dprint fmt") .run() .with_context(|| "Failed to format markdown files")?; println!("✓ Book formatting complete!"); Ok(()) } else { println!("Checking markdown formatting..."); match cmd!(sh, "dprint check").run() { Ok(_) => { println!("✓ All markdown files are correctly formatted!"); Ok(()) } Err(_) => { eprintln!("✗ Some markdown files need formatting."); eprintln!("Run `cargo xtask format-book --fix` to fix them."); bail!("Book formatting check failed"); } } } } ================================================ FILE: xtask/src/format_templates.rs ================================================ use anyhow::{Context, Result, bail}; use xshell::{Shell, cmd}; fn convert_sailfish_tags_to_comment(mut template: &str) -> Result<(String, Vec<&str>)> { let mut result_text = String::with_capacity(template.len()); let mut result_dicts = vec![]; while let Some((before_start, rest)) = template.split_once("<%") { result_text.push_str(before_start); let comment = format!("/* SAILFISH_TEMPLATE{} */", result_dicts.len()); let (tag, rest) = rest .split_once("%>") .context("A <% without %> found in template")?; result_dicts.push(tag); template = rest; result_text.push_str(&comment); } result_text.push_str(template); Ok((result_text, result_dicts)) } fn convert_comments_to_sailfish_tag(mut template: String, tags: Vec<&str>) -> String { for (i, tag) in tags.into_iter().enumerate() { let comment = format!("/* SAILFISH_TEMPLATE{i} */"); let tag_fixed = format!("<%{tag}%>"); template = template.replace(&comment, &tag_fixed); } template } pub fn main(fix: bool) -> Result<()> { let sh = Shell::new()?; let temp_dir = sh.create_temp_dir()?; for cpp_template in [ "./zngur-generator/templates/cpp_header.sptl", "./zngur-generator/templates/cpp_source.sptl", ] { let text = std::fs::read_to_string(cpp_template).context("failed to open template file")?; let (commented_text, tags) = convert_sailfish_tags_to_comment(&text)?; let temp_file_path = temp_dir.path().join("template.cpp"); std::fs::write(&temp_file_path, commented_text)?; cmd!(sh, "clang-format --style=webkit -i {temp_file_path}").run()?; let fixed_text = std::fs::read_to_string(&temp_file_path)?; let fixed_text = convert_comments_to_sailfish_tag(fixed_text, tags); if fix { std::fs::write(&cpp_template, fixed_text)?; } else { if fixed_text != text { bail!("Diff detected in file {cpp_template}"); } } } Ok(()) } ================================================ FILE: xtask/src/main.rs ================================================ use clap::Parser; mod ci; mod format_book; mod format_templates; #[derive(Parser)] enum Command { CI { #[arg(long)] fix: bool, }, FormatBook { #[arg(long)] fix: bool, }, FormatTemplates { #[arg(long)] fix: bool, }, } fn main() -> anyhow::Result<()> { let cmd = Command::parse(); match cmd { Command::CI { fix } => ci::main(fix), Command::FormatBook { fix } => format_book::main(fix), Command::FormatTemplates { fix } => format_templates::main(fix), } } ================================================ FILE: zngur/Cargo.toml ================================================ [package] name = "zngur" description = "A Rust/C++ interoperability tool" readme = "../README.md" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] zngur-generator = { version = "=0.9.0", path = "../zngur-generator" } ================================================ FILE: zngur/src/lib.rs ================================================ //! This crate contains an API for using the Zngur code generator inside build scripts. For more information //! about the Zngur itself, see [the documentation](https://hkalbasi.github.io/zngur). use std::{ fs::File, io::Write, path::{Path, PathBuf}, }; use zngur_generator::{ ParsedZngFile, ZngHeaderGenerator, ZngurGenerator, cfg::{InMemoryRustCfgProvider, NullCfg, RustCfgProvider}, }; #[must_use] /// Builder for the Zngur generator. /// /// Usage: /// ```ignore /// let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); /// let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); /// Zngur::from_zng_file(crate_dir.join("main.zng")) /// .with_cpp_file(out_dir.join("generated.cpp")) /// .with_h_file(out_dir.join("generated.h")) /// .with_rs_file(out_dir.join("generated.rs")) /// .with_depfile(out_dir.join("zngur.d")) /// .generate(); /// ``` pub struct Zngur { zng_file: PathBuf, h_file_path: Option, cpp_file_path: Option, rs_file_path: Option, depfile_path: Option, mangling_base: Option, cpp_namespace: Option, rust_cfg: Option>, zng_header_in_place: bool, zng_h_file_path: Option, crate_name: Option, } impl Zngur { pub fn from_zng_file(zng_file_path: impl AsRef) -> Self { Zngur { zng_file: zng_file_path.as_ref().to_owned(), h_file_path: None, cpp_file_path: None, rs_file_path: None, depfile_path: None, mangling_base: None, cpp_namespace: None, rust_cfg: None, zng_header_in_place: false, zng_h_file_path: None, crate_name: None, } } pub fn with_zng_header(mut self, zng_header: impl AsRef) -> Self { self.zng_h_file_path.replace(zng_header.as_ref().to_owned()); self } pub fn with_h_file(mut self, path: impl AsRef) -> Self { self.h_file_path = Some(path.as_ref().to_owned()); self } pub fn with_cpp_file(mut self, path: impl AsRef) -> Self { self.cpp_file_path = Some(path.as_ref().to_owned()); self } pub fn with_rs_file(mut self, path: impl AsRef) -> Self { self.rs_file_path = Some(path.as_ref().to_owned()); self } /// Set the path for the dependency file (.d file) output. /// /// The dependency file lists all .zng files that were processed (main file + imports). /// This can be used by build systems to detect when regeneration is needed. pub fn with_depfile(mut self, path: impl AsRef) -> Self { self.depfile_path = Some(path.as_ref().to_owned()); self } pub fn with_mangling_base(mut self, mangling_base: &str) -> Self { self.mangling_base = Some(mangling_base.to_owned()); self } pub fn with_cpp_namespace(mut self, cpp_namespace: &str) -> Self { self.cpp_namespace = Some(cpp_namespace.to_owned()); self } pub fn with_crate_name(mut self, crate_name: &str) -> Self { self.crate_name = Some(crate_name.to_owned()); self } pub fn with_rust_cargo_cfg(mut self) -> Self { self.rust_cfg = Some(Box::new( InMemoryRustCfgProvider::default().load_from_cargo_env(), )); self } pub fn with_zng_header_in_place_as(mut self, value: bool) -> Self { self.zng_header_in_place = value; self } pub fn with_zng_header_in_place(self) -> Self { self.with_zng_header_in_place_as(true) } pub fn with_rust_in_memory_cfg<'a, CfgPairs, CfgKey, CfgValues>( mut self, cfg_values: CfgPairs, ) -> Self where CfgPairs: IntoIterator, CfgKey: AsRef + 'a, CfgValues: Clone + IntoIterator + 'a, ::Item: AsRef, { self.rust_cfg = Some(Box::new( InMemoryRustCfgProvider::default().with_values(cfg_values), )); self } pub fn generate(self) { let rust_cfg = self.rust_cfg.unwrap_or_else(|| Box::new(NullCfg)); let parse_result = ParsedZngFile::parse(self.zng_file, rust_cfg); let crate_name = self .crate_name .or_else(|| std::env::var("CARGO_PKG_NAME").ok()) .unwrap_or_else(|| "crate".to_owned()); // We will pass crate_name to ZngurGenerator instead of mutating spec. let panic_to_exception = parse_result.spec.convert_panic_to_exception.0; let mut file = ZngurGenerator::build_from_zng(parse_result.spec, crate_name); let rs_file_path = self.rs_file_path.expect("No rs file path provided"); let h_file_path = self.h_file_path.expect("No h file path provided"); file.0.cpp_include_header_name = h_file_path .file_name() .unwrap() .to_string_lossy() .into_owned(); if let Some(cpp_namespace) = &self.cpp_namespace { file.0.mangling_base = cpp_namespace.clone(); file.0.cpp_namespace = Some(cpp_namespace.clone()); } if let Some(mangling_base) = self.mangling_base.or_else(|| file.0.cpp_namespace.clone()) { // println!("Mangling: {mangling_base}"); file.0.mangling_base = mangling_base; } let (rust, h, cpp) = file.render(self.zng_header_in_place); File::create(&rs_file_path) .unwrap() .write_all(rust.as_bytes()) .unwrap(); File::create(&h_file_path) .unwrap() .write_all(h.as_bytes()) .unwrap(); if let Some(cpp) = &cpp { let cpp_file_path = self .cpp_file_path .as_ref() .expect("No cpp file path provided"); File::create(cpp_file_path) .unwrap() .write_all(cpp.as_bytes()) .unwrap(); } // Write dependency file if requested if let Some(depfile_path) = self.depfile_path { let mut targets = vec![ h_file_path.display().to_string(), rs_file_path.display().to_string(), ]; if let Some(cpp_path) = self.cpp_file_path { if cpp.is_some() { targets.push(cpp_path.display().to_string()); } } // Format: "target1 target2: dep1 dep2 dep3" let deps: Vec = parse_result .processed_files .iter() .map(|p| p.display().to_string()) .collect(); let depfile_content = format!("{}: {}\n", targets.join(" "), deps.join(" ")); File::create(depfile_path) .unwrap() .write_all(depfile_content.as_bytes()) .unwrap(); } if let Some(zng_h) = self.zng_h_file_path { let mut zng = ZngurHdr::new() .with_panic_to_exception_as(panic_to_exception) .with_zng_header(zng_h); if let Some(cpp_namespace) = &self.cpp_namespace { zng = zng.with_cpp_namespace(&cpp_namespace); } zng.generate(); } } } #[derive(Debug)] pub struct ZngurHdr { panic_to_exception: bool, zng_header_file: Option, cpp_namespace: Option, } impl ZngurHdr { pub const fn new() -> Self { Self { panic_to_exception: false, zng_header_file: None, cpp_namespace: None, } } pub fn with_panic_to_exception(self) -> Self { self.with_panic_to_exception_as(true) } pub fn without_panic_to_exception(self) -> Self { self.with_panic_to_exception_as(false) } pub fn with_panic_to_exception_as(mut self, panic_to_exception: bool) -> Self { self.panic_to_exception = panic_to_exception; self } pub fn with_zng_header(mut self, zng_header: impl Into) -> Self { self.zng_header_file.replace(zng_header.into()); self } pub fn with_cpp_namespace(mut self, cpp_namespace: &str) -> Self { self.cpp_namespace = Some(cpp_namespace.to_owned()); self } pub fn generate(self) { let generator = ZngHeaderGenerator { panic_to_exception: self.panic_to_exception, cpp_namespace: self.cpp_namespace.unwrap_or_else(|| "rust".to_owned()), }; let out_h = self .zng_header_file .expect("Missing zng header output file"); let rendered = generator.render(); std::fs::write(&out_h, rendered) .unwrap_or_else(|_| panic!("Couldn't write contents to {}", out_h.display())); } } ================================================ FILE: zngur-autozng/Cargo.toml ================================================ [package] name = "zngur-autozng" description = "Generating Zngur zng files automatically for a Rust crate" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true [dependencies] serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.122" ================================================ FILE: zngur-autozng/src/main.rs ================================================ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] enum RustdocRustType { BorrowedRef { mutable: bool, #[serde(rename = "type")] inner: Box, }, RawPointer { mutable: bool, #[serde(rename = "type")] inner: Box, }, Primitive(String), Generic(String), Tuple(Vec), Slice(Box), ResolvedPath { name: String, }, QualifiedPath {}, } impl RustdocRustType { fn render(&self) -> String { match self { RustdocRustType::BorrowedRef { mutable: false, inner, } => format!("&{}", inner.render()), RustdocRustType::BorrowedRef { mutable: true, inner, } => format!("&mut {}", inner.render()), RustdocRustType::RawPointer { .. } => todo!(), RustdocRustType::Primitive(n) => n.clone(), RustdocRustType::Generic(n) => n.clone(), RustdocRustType::Tuple(_) => todo!(), RustdocRustType::Slice(_) => todo!(), RustdocRustType::ResolvedPath { name } => name.clone(), RustdocRustType::QualifiedPath {} => todo!(), } } } #[derive(Debug, Serialize, Deserialize)] struct RustdocFunctionDecl { inputs: Vec<(String, RustdocRustType)>, output: Option, #[serde(flatten)] other_fields: Value, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] enum RustdocItemInner { Function { decl: RustdocFunctionDecl, #[serde(flatten)] other_fields: Value, }, Struct { impls: Vec, #[serde(flatten)] other_fields: Value, }, Impl { items: Vec, #[serde(rename = "trait")] for_trait: Option, #[serde(flatten)] other_fields: Value, }, Module { #[serde(flatten)] other_fields: Value, }, StructField { #[serde(flatten)] other_fields: Value, }, Import { #[serde(flatten)] other_fields: Value, }, AssocType { #[serde(flatten)] other_fields: Value, }, Variant { #[serde(flatten)] other_fields: Value, }, TypeAlias { #[serde(flatten)] other_fields: Value, }, Enum { impls: Vec, #[serde(flatten)] other_fields: Value, }, } #[derive(Debug, Serialize, Deserialize)] struct RustdocItem { name: Option, inner: RustdocItemInner, #[serde(flatten)] other_fields: Value, } #[derive(Serialize, Deserialize)] struct RustdocOutput { index: HashMap, } fn main() { let s = std::fs::read_to_string("./doc.json").unwrap(); let d: RustdocOutput = serde_json::from_str(&s).unwrap(); for x in &d.index { if let RustdocItemInner::Struct { impls, .. } | RustdocItemInner::Enum { impls, .. } = &x.1.inner { println!("type crate::{} {{", x.1.name.as_ref().unwrap()); println!(" #heap_allocated;"); for imp in impls { let imp = &d.index[imp]; // dbg!(imp); if let RustdocItemInner::Impl { items, for_trait, .. } = &imp.inner { if for_trait.is_some() { continue; } for item in items { let item = &d.index[item]; if let RustdocItemInner::Function { decl: RustdocFunctionDecl { inputs, output, .. }, .. } = &item.inner { print!(" fn {}(", item.name.as_deref().unwrap()); let mut first = true; for (name, ty) in inputs { if !first { print!(", "); } first = false; if name == "self" { print!("self"); continue; } print!("{}", ty.render()); } print!(")"); if let Some(output) = output { print!(" -> {}", output.render()); } println!(";"); } } } } println!("}}"); } } } ================================================ FILE: zngur-cli/Cargo.toml ================================================ [package] name = "zngur-cli" description = "CLI of the Zngur, a Rust/C++ interoperability tool" readme = "../README.md" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true [[bin]] name = "zngur" path = "src/main.rs" bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] zngur = { version = "=0.9.0", path = "../zngur" } clap = { version = "4.3.12", features = ["derive"] } ================================================ FILE: zngur-cli/src/cfg_extractor.rs ================================================ use clap::Args; use std::collections::HashMap; #[derive(Args)] pub struct CfgFromRustc { /// Load rust cfg values using `rustc --print cfg` /// /// values loaded are in addition to any values provided with `--cfg` or `--feature` /// respects the `RUSTFLAGS` environment variable #[arg(long, group = "rustc")] pub load_cfg_from_rustc: bool, /// Use `cargo rustc -- --print cfg` which allows automatically collecting profile flags and /// features /// /// WARNING: because `cargo rustc --print` is unstable this uses `cargo rustc -- --print` which /// may invoke side effects like downloading all crate dependencies /// /// (requires --load-cfg-from-rustc) #[arg(long, group = "cargo_rustc", requires = "rustc")] use_cargo_rustc: bool, /// flags to pass to rustc when loading cfg values /// /// (requires --load-cfg-from-rustc) #[arg(long, requires = "rustc")] rustc_flags: Option, /// A target to provide to `rustc` when loading config values /// /// (requires --load-cfg-from-rustc) #[arg(long = "target", requires = "rustc")] rustc_target: Option, /// cargo profile to use when loading cfg values /// /// (requires --use-cargo-rustc) #[arg(long = "profile", requires = "cargo_rustc")] cargo_profile: Option, /// cargo package to use when loading cfg values /// /// (requires --use-cargo-rustc) #[arg(long = "package", requires = "cargo_rustc")] cargo_package: Option, /// passes --no-default-features to cargo /// /// (requires --use-cargo-rustc) #[arg(long, requires = "cargo_rustc")] no_default_features: bool, /// passes --all_features to cargo /// /// (requires --use-cargo-rustc) #[arg(long, requires = "cargo_rustc")] all_features: bool, } pub fn cfg_from_rustc( args: CfgFromRustc, rust_features: &[String], ) -> HashMap> { let mut cfg: HashMap> = HashMap::new(); let mut rustflags = parse_rustflags_env(); if let Some(flags) = &args.rustc_flags { rustflags.extend(parse_rustflags(flags)) } let mut cmd = if args.use_cargo_rustc { let mut cmd = std::process::Command::new("cargo"); cmd.arg("rustc"); if let Some(package) = &args.cargo_package { cmd.args(["--package", package]); } if let Some(profile) = &args.cargo_profile { cmd.args(["--profile", profile]); } if !rust_features.is_empty() && !args.all_features { cmd.args(["--features", &rust_features.join(",")]); } if args.all_features { cmd.arg("--all-features"); } if args.no_default_features { cmd.arg("--no-default-features"); } cmd } else { std::process::Command::new("rustc") }; if let Some(target) = &args.rustc_target { cmd.args(["--target", target]); } if args.use_cargo_rustc { cmd.arg("--"); } cmd.args(rustflags); cmd.args(["--print", "cfg"]); let out = cmd.output().expect("failed to print cfg with rustc"); if !out.status.success() { eprintln!("{}", String::from_utf8_lossy(&out.stderr)); std::process::exit(1); } let out = String::from_utf8(out.stdout).expect("failed to parse rustc output as utf8"); let lines = out.split('\n').collect::>(); for line in lines { if line.trim().is_empty() { continue; } let (key, value) = line.trim().split_once('=').unwrap_or((line, "")); let value = if value.len() >= 2 && value.starts_with('"') && value.ends_with('"') { &value[1..value.len() - 1] } else { value }; let entry = cfg.entry(key.to_owned()).or_default(); entry.push(value.to_owned()) } cfg } fn parse_rustflags_env() -> Vec { let flags_str = std::env::var("RUSTFLAGS").unwrap_or_default(); parse_rustflags(&flags_str) } fn parse_rustflags(flags_str: &str) -> Vec { let mut word: String = String::new(); let mut flags: Vec = Vec::new(); #[derive(Copy, Clone)] enum State { Delem, Unquoted, Escape(&'static State), Single, Double, } use State::*; let mut state = Delem; let mut chars = flags_str.chars(); loop { let Some(c) = chars.next() else { match state { Delem => break, Unquoted | Single | Double => { flags.push(std::mem::take(&mut word)); break; } Escape(_) => { word.push('\\'); flags.push(std::mem::take(&mut word)); break; } } }; state = match state { Delem => match c { '\'' => Single, '"' => Double, '\\' => Escape(&Delem), '\t' | ' ' | '\n' => Delem, c => { word.push(c); Unquoted } }, Unquoted => match c { '\'' => Single, '"' => Double, '\\' => Escape(&Unquoted), '\t' | ' ' | '\n' => { flags.push(std::mem::take(&mut word)); Delem } c => { word.push(c); Unquoted } }, Escape(next_state) => match c { c @ '"' | c @ '\\' if matches!(next_state, Double) => { word.push(c); Double } '\n' => *next_state, c => { word.push('\\'); word.push(c); *next_state } }, Single => match c { '\'' => Unquoted, c => { word.push(c); Single } }, Double => match c { '"' => Unquoted, '\\' => Escape(&Double), c => { word.push(c); Double } }, } } flags } ================================================ FILE: zngur-cli/src/main.rs ================================================ use std::{collections::HashMap, path::PathBuf}; use clap::Parser; use zngur::{Zngur, ZngurHdr}; use crate::cfg_extractor::{CfgFromRustc, cfg_from_rustc}; mod cfg_extractor; #[derive(Clone)] struct CfgKey { pub key: String, pub values: Vec, } impl CfgKey { fn into_tuple(self) -> (String, Vec) { (self.key, self.values) } } impl<'a> From<&'a str> for CfgKey { fn from(s: &'a str) -> Self { let (key, values_s) = s.split_once('=').unwrap_or((s, "")); let values: Vec = values_s .split(',') .map(|s| { (if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') { &s[1..s.len() - 1] } else { s }) .to_owned() }) .collect(); CfgKey { key: key.to_owned(), values, } } } #[derive(Parser)] #[command(version)] enum Command { #[command(alias = "g")] Generate { /// Path to the zng file path: PathBuf, /// Path of the generated C++ file, if it is needed /// /// Default is {ZNG_FILE_PARENT}/generated.cpp #[arg(long)] cpp_file: Option, /// Path of the generated header file /// /// Default is {ZNG_FILE_PARENT}/generated.h #[arg(long)] h_file: Option, /// Path of the generated Rust file /// /// Default is {ZNG_FILE_PARENT}/src/generated.rs #[arg(long)] rs_file: Option, /// Path of the dependency file (.d file) to generate /// /// The dependency file lists all .zng files that were processed. /// This can be used by build systems to detect when regeneration is needed. #[arg(long)] depfile: Option, /// A unique string which is included in zngur symbols to prevent duplicate /// symbols in linker /// /// Default is "rust" #[arg(long)] mangling_base: Option, /// A rust config value of the form key(=value1(,value2 ...)) to use when /// generating the zngur spec. /// i.e. -C target_os=linux -C target_feature=sse,sse2 -C debug_assertions /// /// see https://doc.rust-lang.org/reference/conditional-compilation.html /// for possible values /// /// combined with any values loaded from rustc (if enabled) /// /// Default is an empty configuration #[arg(long = "cfg", short = 'C')] rust_cfg: Vec, /// A feature name to enable when generating the zngur spec /// /// combined with any values loaded from rustc (if enabled) /// /// Default is no features #[arg(long = "feature", short = 'F')] rust_features: Vec, #[command(flatten)] load_rustc_cfg: CfgFromRustc, /// When set, the generator will embed the common Zngur types into the generated header. /// /// The recommended workflow is to generate a `zngur.h` header with `make-zng-header` which should /// be shared between all of your generated modules and leave this flag unset. #[arg(long = "in-place", short = 'i')] zng_header_in_place: bool, /// Set the namespace of the generated C++ types. If not provided it defaults to `rust` /// /// You may use this to customize overarching namespace for all the /// generated types in C++, but if your project spans multiple .zng modules, you /// must use a shared namespace #[arg(long)] cpp_namespace: Option, /// Set the crate name to be used in the generated code where `crate` appears. /// /// If not provided, it tries to read `CARGO_PKG_NAME` and defaults to `crate` if unset. #[arg(long)] crate_name: Option, }, #[command(alias = "h")] /// Generates the zngur.h file that contains shared interop definitions used by all generated zngur bridges. MakeZngHeader { /// Path to the generated header file path: PathBuf, /// If set, it will generate the "panic to exceptions" mechanism in the generated header. /// /// Note that each `zng` module will need to use `#convert_panic_to_exception` in order to fully enable it. #[arg(long = "panic-to-exception")] convert_panic_to_exception: bool, /// Set the namespace of the generated C++ types. If not provided it defaults to `rust` /// /// You may use this to customize overarching namespace for all the /// generated types in C++, but if your project spans multiple .zng modules, you /// must use a shared namespace #[arg(long)] cpp_namespace: Option, }, } fn main() { let cmd = Command::parse(); match cmd { Command::Generate { path, cpp_file, h_file, rs_file, depfile, mangling_base, rust_cfg, rust_features, load_rustc_cfg, zng_header_in_place, cpp_namespace, crate_name, } => { let pp = path.parent().unwrap(); let cpp_file = cpp_file.unwrap_or_else(|| pp.join("generated.cpp")); let h_file = h_file.unwrap_or_else(|| pp.join("generated.h")); let rs_file = rs_file.unwrap_or_else(|| pp.join("src/generated.rs")); let mut zng = Zngur::from_zng_file(&path) .with_cpp_file(cpp_file) .with_h_file(h_file) .with_rs_file(rs_file) .with_zng_header_in_place_as(zng_header_in_place); if let Some(cpp_namespace) = cpp_namespace { zng = zng.with_cpp_namespace(&cpp_namespace); } if let Some(crate_name) = crate_name { zng = zng.with_crate_name(&crate_name); } let mut cfg: HashMap> = HashMap::new(); if load_rustc_cfg.load_cfg_from_rustc { cfg.extend(cfg_from_rustc(load_rustc_cfg, &rust_features)); } if !rust_cfg.is_empty() { cfg.extend(rust_cfg.into_iter().map(CfgKey::into_tuple)); } if !rust_features.is_empty() { cfg.insert("feature".to_owned(), rust_features); } if !cfg.is_empty() { zng = zng.with_rust_in_memory_cfg(cfg); } if let Some(depfile) = depfile { zng = zng.with_depfile(depfile); } if let Some(mangling_base) = mangling_base { zng = zng.with_mangling_base(&mangling_base); } zng.generate(); } Command::MakeZngHeader { path, convert_panic_to_exception, cpp_namespace, } => { let mut hdr = ZngurHdr::new() .with_panic_to_exception_as(convert_panic_to_exception) .with_zng_header(path); if let Some(cpp_namespace) = cpp_namespace { hdr = hdr.with_cpp_namespace(&cpp_namespace); } hdr.generate(); } } } ================================================ FILE: zngur-def/Cargo.toml ================================================ [package] name = "zngur-def" description = "Data types that define the structure of a zng file" readme = "../README.md" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] indexmap = "2.13.0" itertools = "0.11" ================================================ FILE: zngur-def/src/lib.rs ================================================ use std::fmt::Display; use indexmap::IndexMap; use itertools::Itertools; mod merge; pub use merge::{Merge, MergeFailure, MergeResult}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Mutability { Mut, Not, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ZngurMethodReceiver { Static, Ref(Mutability), Move, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ZngurMethod { pub name: String, pub generics: Vec, pub receiver: ZngurMethodReceiver, pub inputs: Vec, pub output: RustType, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ZngurFn { pub path: RustPathAndGenerics, pub inputs: Vec, pub output: RustType, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ZngurExternCppFn { pub name: String, pub inputs: Vec, pub output: RustType, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ZngurExternCppImpl { pub tr: Option, pub ty: RustType, pub methods: Vec, } #[derive(Debug, PartialEq, Eq)] pub struct ZngurConstructor { pub name: Option, pub inputs: Vec<(String, RustType)>, } #[derive(Debug, PartialEq, Eq)] pub struct ZngurField { pub name: String, pub ty: RustType, pub offset: Option, } #[derive(Debug, PartialEq, Eq)] pub struct ZngurFieldData { pub name: String, pub ty: RustType, pub offset: ZngurFieldDataOffset, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ZngurFieldDataOffset { Offset(usize), Auto(String), } impl ZngurFieldDataOffset { pub fn is_offset(&self) -> bool { matches!(self, ZngurFieldDataOffset::Offset(_)) } pub fn as_offset(&self) -> Option { match self { ZngurFieldDataOffset::Offset(o) => Some(*o), _ => None, } } pub fn as_auto(&self) -> Option<&str> { match self { ZngurFieldDataOffset::Auto(s) => Some(s), _ => None, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ZngurWellknownTrait { Debug, Drop, Unsized, Copy, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ZngurWellknownTraitData { Debug { pretty_print: String, debug_print: String, }, Drop { drop_in_place: String, }, Unsized, Copy, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LayoutPolicy { StackAllocated { size: usize, align: usize }, Conservative { size: usize, align: usize }, HeapAllocated, OnlyByRef, } impl LayoutPolicy { pub const ZERO_SIZED_TYPE: Self = LayoutPolicy::StackAllocated { size: 0, align: 1 }; } #[derive(Debug, PartialEq, Eq)] pub struct ZngurMethodDetails { pub data: ZngurMethod, pub use_path: Option>, pub deref: Option<(RustType, Mutability)>, } #[derive(Debug, PartialEq, Eq)] pub struct CppValue(pub String, pub String); #[derive(Debug, PartialEq, Eq)] pub struct CppRef(pub String); impl Display for CppRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Debug)] pub struct ZngurType { pub ty: RustType, pub layout: LayoutPolicy, pub wellknown_traits: Vec, pub methods: Vec, pub constructors: Vec, pub fields: Vec, pub cpp_value: Option, pub cpp_ref: Option, } #[derive(Debug)] pub struct ZngurTrait { pub tr: RustTrait, pub methods: Vec, } #[derive(Debug, Default)] pub struct AdditionalIncludes(pub String); #[derive(Debug, Default)] pub struct ConvertPanicToException(pub bool); #[derive(Clone, Debug, Default)] pub struct Import(pub std::path::PathBuf); #[derive(Debug, Clone)] pub struct ModuleImport { pub path: std::path::PathBuf, } #[derive(Debug, Default)] pub struct ZngurSpec { pub imports: Vec, pub imported_modules: Vec, pub types: Vec, pub traits: IndexMap, pub funcs: Vec, pub extern_cpp_funcs: Vec, pub extern_cpp_impls: Vec, pub additional_includes: AdditionalIncludes, pub convert_panic_to_exception: ConvertPanicToException, pub cpp_include_header_name: String, pub mangling_base: String, pub cpp_namespace: Option, pub rust_cfg: Vec<(String, Option)>, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum RustTrait { Normal(RustPathAndGenerics), Fn { name: String, inputs: Vec, output: Box, }, } impl RustTrait { pub fn take_assocs(mut self) -> (Self, Vec<(String, RustType)>) { let assocs = match &mut self { RustTrait::Normal(p) => std::mem::take(&mut p.named_generics), RustTrait::Fn { .. } => vec![], }; (self, assocs) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum PrimitiveRustType { Uint(u32), Int(u32), Float(u32), Usize, Bool, Char, Str, ZngurCppOpaqueOwnedObject, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RustPathAndGenerics { pub path: Vec, pub generics: Vec, pub named_generics: Vec<(String, RustType)>, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum RustType { Primitive(PrimitiveRustType), Ref(Mutability, Box), Raw(Mutability, Box), Boxed(Box), Slice(Box), Dyn(RustTrait, Vec), Impl(RustTrait, Vec), Tuple(Vec), Adt(RustPathAndGenerics), } impl RustType { pub const UNIT: Self = RustType::Tuple(Vec::new()); } impl Display for RustPathAndGenerics { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let RustPathAndGenerics { path, generics, named_generics, } = self; for p in path { if p != "crate" { write!(f, "::")?; } write!(f, "{p}")?; } if !generics.is_empty() || !named_generics.is_empty() { write!( f, "::<{}>", generics .iter() .map(|x| format!("{x}")) .chain(named_generics.iter().map(|x| format!("{} = {}", x.0, x.1))) .join(", ") )?; } Ok(()) } } impl Display for RustTrait { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RustTrait::Normal(tr) => write!(f, "{tr}"), RustTrait::Fn { name, inputs, output, } => { write!(f, "{name}({})", inputs.iter().join(", "))?; if **output != RustType::UNIT { write!(f, " -> {output}")?; } Ok(()) } } } } impl Display for RustType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RustType::Primitive(s) => match s { PrimitiveRustType::Uint(s) => write!(f, "u{s}"), PrimitiveRustType::Int(s) => write!(f, "i{s}"), PrimitiveRustType::Float(s) => write!(f, "f{s}"), PrimitiveRustType::Usize => write!(f, "usize"), PrimitiveRustType::Bool => write!(f, "bool"), PrimitiveRustType::Char => write!(f, "char"), PrimitiveRustType::Str => write!(f, "str"), PrimitiveRustType::ZngurCppOpaqueOwnedObject => { write!(f, "ZngurCppOpaqueOwnedObject") } }, RustType::Ref(Mutability::Not, ty) => write!(f, "&{ty}"), RustType::Ref(Mutability::Mut, ty) => write!(f, "&mut {ty}"), RustType::Raw(Mutability::Not, ty) => write!(f, "*const {ty}"), RustType::Raw(Mutability::Mut, ty) => write!(f, "*mut {ty}"), RustType::Boxed(ty) => write!(f, "Box<{ty}>"), RustType::Tuple(v) => write!(f, "({})", v.iter().join(", ")), RustType::Adt(pg) => write!(f, "{pg}"), RustType::Dyn(tr, marker_bounds) => { write!(f, "dyn {tr}")?; for mb in marker_bounds { write!(f, "+ {mb}")?; } Ok(()) } RustType::Impl(tr, marker_bounds) => { write!(f, "impl {tr}")?; for mb in marker_bounds { write!(f, "+ {mb}")?; } Ok(()) } RustType::Slice(s) => write!(f, "[{s}]"), } } } ================================================ FILE: zngur-def/src/merge.rs ================================================ use crate::{ AdditionalIncludes, ConvertPanicToException, CppRef, CppValue, LayoutPolicy, ZngurConstructor, ZngurExternCppFn, ZngurExternCppImpl, ZngurField, ZngurFn, ZngurMethodDetails, ZngurSpec, ZngurTrait, ZngurType, }; /// Trait for types with a partial union operation. /// /// If a type T is Merge, it provides a partial union operation `merge`: T x T -> T. /// /// Partial unions do not need to be homogenous. If a type U is Merge, /// it provides a partial union operation `merge`: T X U -> U. /// For example, T: usize, U: Set; the partial union is the result of /// adding the lhs usize to the rhs Set. /// /// "Partial" means the result is not necessarily defined for all inputs; the union may fail. /// This is often because the instances are contradictory (as defined by the type). /// /// There are no guarantees about the state of the mutable argument, `into`, in the case /// of a failed merge. `merge` is not required to leave `into` in a valid state, or restore /// it to its original state. pub trait Merge { /// Writes the partial union of `self` and `into` to the latter. /// /// # Errors /// /// If the instances are contradictory, a `MergeFailure` is returned. fn merge(self, into: &mut T) -> MergeResult; } /// The result of a merge operation. pub type MergeResult = Result<(), MergeFailure>; /// An unsuccessful merge operation. pub enum MergeFailure { /// The merge was not successful because of a conflict. Conflict(String), } /// Push an item onto a vector if it is not already present, in linear time. fn push_unique(item: T, smallvec: &mut std::vec::Vec) { if !smallvec.contains(&item) { smallvec.push(item); } } /// Writes the union of `other` and `smallvec` to the latter in O(N * M) time. fn inplace_union(other: Vec, smallvec: &mut std::vec::Vec) { for item in other { push_unique(item, smallvec); } } /// Writes the union of `other` and `smallvec` to the latter in O(N * M) time. /// If an element in `other` has the same identity as an element in `smallvec`, /// the elements are merged. Otherwise, the element is added to `smallvec`. fn merge_by_identity( other: Vec, smallvec: &mut std::vec::Vec, identity: impl Fn(&T, &T) -> bool, ) -> MergeResult { for item in other { if let Some(existing) = smallvec.iter_mut().find(|e| identity(e, &item)) { item.merge(existing)?; } else { smallvec.push(item); } } Ok(()) } impl Merge for Option { /// Writes the partial union of `self` and `into` to the latter. /// /// If both `self` and `into` are Some, the underlying values are merged. /// Otherwise, the result is whichever value is Some, or None if neither is. fn merge(self, into: &mut Self) -> MergeResult { match self { Some(src) => match into.as_mut() { Some(dst) => src.merge(dst), None => { *into = Some(src); Ok(()) } }, None => Ok(()), } } } impl> Merge> for I where K: Eq + std::hash::Hash, V: Merge, { /// Merges a sequence of key-value pairs into a map. /// /// If a key is present in both `self` and `into`, the corresponding values are merged. /// Otherwise, the entry from `self` is inserted into `into`. /// /// This implementation implies `indexmap::IndexMap` is `Merge` for all `V: Merge`, /// because IndexMap is `IntoIterator`. We use `IntoIterator` to allow literal sequences of /// key-value pairs to be merged into a map. fn merge(self, into: &mut indexmap::IndexMap) -> MergeResult { for (key, value) in self { match into.entry(key) { indexmap::map::Entry::Vacant(e) => { e.insert(value); } indexmap::map::Entry::Occupied(mut e) => match value.merge(e.get_mut()) { Ok(()) => {} Err(message) => { return Err(message); } }, } } Ok(()) } } impl Merge for ZngurType { /// Writes the partial union of `self` and `into` to the latter. /// /// PRECONDITION: `self.ty == into.ty`. fn merge(self, into: &mut Self) -> MergeResult { if self.ty != into.ty { panic!( "Attempt to merge different types: {} and {}", self.ty, into.ty ); } if self.layout != into.layout { return Err(MergeFailure::Conflict( "Duplicate layout policy found".to_string(), )); } // TODO: We need to improve the architecture around checking parsing, semantic, and other types of errors. if self.cpp_ref.is_some() && into.layout != LayoutPolicy::ZERO_SIZED_TYPE { return Err(MergeFailure::Conflict( "cpp_ref implies a zero sized stack allocated type".to_string(), )); } self.cpp_value.merge(&mut into.cpp_value)?; self.cpp_ref.merge(&mut into.cpp_ref)?; inplace_union(self.wellknown_traits, &mut into.wellknown_traits); merge_by_identity(self.methods, &mut into.methods, |a, b| { a.data.name == b.data.name })?; merge_by_identity(self.constructors, &mut into.constructors, |a, b| { a.name == b.name })?; merge_by_identity(self.fields, &mut into.fields, |a, b| a.name == b.name)?; Ok(()) } } impl Merge for ZngurTrait { /// Writes the partial union of `self` and `into` to the latter. /// /// PRECONDITION: `self.tr == into.tr`. fn merge(self, into: &mut Self) -> MergeResult { if self.tr != into.tr { panic!( "Attempt to merge different traits: {} and {}", self.tr, into.tr ); } inplace_union(self.methods, &mut into.methods); Ok(()) } } impl Merge for CppValue { /// Writes the partial union of `self` and `into` to the latter. /// /// There is no meaningful way to merge different CppValues, but we allow /// merging the same CppValue from different sources. fn merge(self, into: &mut Self) -> MergeResult { if self != *into { return Err(MergeFailure::Conflict("Cpp value mismatch".to_string())); } Ok(()) } } impl Merge for CppRef { /// Writes the partial union of `self` and `into` to the latter. /// /// There is no meaningful way to merge different CppRefs, but we allow /// merging the same CppRef from different sources. fn merge(self, into: &mut Self) -> MergeResult { if self != *into { return Err(MergeFailure::Conflict("Cpp ref mismatch".to_string())); } Ok(()) } } impl Merge for ZngurType { /// Merges a type into a specification's type list. fn merge(self, into: &mut ZngurSpec) -> MergeResult { if let Some(existing) = into.types.iter_mut().find(|t| t.ty == self.ty) { self.merge(existing)?; } else { into.types.push(self); } Ok(()) } } impl Merge for ZngurMethodDetails { fn merge(self, into: &mut Self) -> MergeResult { if self != *into { return Err(MergeFailure::Conflict("Method mismatch".to_string())); } Ok(()) } } impl Merge for ZngurConstructor { fn merge(self, into: &mut Self) -> MergeResult { if self != *into { return Err(MergeFailure::Conflict("Constructor mismatch".to_string())); } Ok(()) } } impl Merge for ZngurField { fn merge(self, into: &mut Self) -> MergeResult { if self != *into { return Err(MergeFailure::Conflict("Field mismatch".to_string())); } Ok(()) } } impl Merge for ZngurTrait { /// Merges a trait into a specification's trait list. fn merge(self, into: &mut ZngurSpec) -> MergeResult { [(self.tr.clone(), self)].merge(&mut into.traits) } } impl Merge for ZngurFn { /// Merges a function into a specification's function list. fn merge(self, into: &mut ZngurSpec) -> MergeResult { push_unique(self, &mut into.funcs); Ok(()) } } impl Merge for ZngurExternCppFn { /// Merges an extern C++ function into a specification's C++ function list. fn merge(self, into: &mut ZngurSpec) -> MergeResult { push_unique(self, &mut into.extern_cpp_funcs); Ok(()) } } impl Merge for ZngurExternCppImpl { /// Merges an extern C++ implementation into a specification's C++ implementation list. fn merge(self, into: &mut ZngurSpec) -> MergeResult { push_unique(self, &mut into.extern_cpp_impls); Ok(()) } } impl Merge for AdditionalIncludes { /// Merges #include directives into a specification's additional includes string. fn merge(self, into: &mut ZngurSpec) -> MergeResult { into.additional_includes.0 += self.0.as_str(); Ok(()) } } impl Merge for ConvertPanicToException { /// Merges a CPtE flag into a specification's CPtE flag. fn merge(self, into: &mut ZngurSpec) -> MergeResult { // TODO: There's an architectural decision here. // I'd like to encode the "unimportability" of CPTE here, rather than in the parser. // But checking this requires knowledge of the parse depth, which seems inappropriate // to pass through here. into.convert_panic_to_exception.0 |= self.0; Ok(()) } } ================================================ FILE: zngur-generator/Cargo.toml ================================================ [package] name = "zngur-generator" description = "Generates Rust and C++ glue codes from the zng file" readme = "../README.md" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] itertools = "0.11" askama = "0.12" zngur-parser = { version = "=0.9.0", path = "../zngur-parser" } zngur-def = { version = "=0.9.0", path = "../zngur-def" } sha2 = "0.10.9" hex = "0.4.3" indexmap = "2.13.0" ================================================ FILE: zngur-generator/src/cpp.rs ================================================ use std::{ fmt::{Display, Write}, iter, }; use indexmap::IndexMap; use itertools::Itertools; use zngur_def::{CppRef, CppValue, RustTrait, ZngurFieldData, ZngurMethodReceiver}; use crate::{ ZngurWellknownTraitData, template::{CppHeaderTemplate, CppSourceTemplate}, }; use askama::Template; #[derive(Debug)] pub struct CppPath(pub Vec); impl CppPath { fn namespace(&self) -> &[String] { self.0.split_last().unwrap().1 } pub(crate) fn open_namespace(&self) -> String { self.namespace() .iter() .enumerate() .map(|(i, x)| format!("{:indent$}namespace {} {{", "", x, indent = i * 4)) .join("\n") } pub(crate) fn close_namespace(&self) -> String { self.namespace() .iter() .enumerate() .map(|(i, x)| format!("{:indent$}}} // namespace {}", "", x, indent = i * 4)) .join("\n") } pub(crate) fn name(&self) -> &str { match self.0.split_last().unwrap().0.as_str() { // Unit is a valid type alias for Tuple<> except when defining the constructors "Unit" => "Tuple", s => s, } } fn need_header(&self) -> bool { if self.0.len() == 1 && self.0[0].ends_with("_t") { return false; } // Top level namespace and type... if self.0.len() == 2 { let ty = &self.0[1]; return ty != "Unit" && ty != "Ref" && ty != "RefMut"; } true } pub(crate) fn from_rust_path(path: &[String], ns: &str, crate_name: &str) -> CppPath { CppPath( iter::once(ns) .chain( path.iter() .map(|x| if x == "crate" { crate_name } else { x.as_str() }), ) .map(cpp_handle_keyword) .map(|x| x.to_owned()) .collect(), ) } } impl From<[&str; N]> for CppPath { fn from(value: [&str; N]) -> Self { CppPath(value.iter().map(|x| x.to_string()).collect()) } } impl From<&str> for CppPath { fn from(value: &str) -> Self { let value = value.trim(); CppPath(value.split("::").map(|x| x.to_owned()).collect()) } } impl Display for CppPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "::{}", self.0.iter().join("::")) } } #[derive(Debug)] pub struct CppType { pub path: CppPath, pub generic_args: Vec, } impl CppType { pub fn into_ref(self) -> CppType { CppType { path: CppPath::from(&*format!("{}::Ref", self.top_level_ns())), generic_args: vec![self], } } fn top_level_ns(&self) -> &String { &self.path.namespace()[0] } pub(crate) fn specialization_decl(&self) -> String { let name = self.path.name(); // Tuple<> is a little special because it is a template even though self.generic_args is empty if self.generic_args.is_empty() && name != "Tuple" { format!("struct {}", name) } else { format!( "template<> struct {}< {} >", name, self.generic_args.iter().join(", ") ) } } fn header_helper(&self, state: &mut impl Write) -> std::fmt::Result { // Note: probably need to keep this out of the template because it's recursive. for x in &self.generic_args { x.header_helper(state)?; } if !self.path.need_header() { return Ok(()); } for p in self.path.namespace() { writeln!(state, "namespace {} {{", p)?; } if !self.generic_args.is_empty() { writeln!(state, "template")?; } writeln!(state, "struct {};", self.path.name())?; for _ in self.path.namespace() { writeln!(state, "}}")?; } Ok(()) } pub(crate) fn header(&self) -> String { let mut state = String::new(); self.header_helper(&mut state).unwrap(); state } } impl Display for CppType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.path)?; if !self.generic_args.is_empty() { write!(f, "< {} >", self.generic_args.iter().join(", "))?; } Ok(()) } } fn split_string(input: &str) -> impl Iterator { let mut parts = Vec::new(); let mut current_part = String::new(); let mut parentheses_count = 0; for c in input.chars() { match c { ',' if parentheses_count == 0 => { parts.push(current_part.clone()); current_part.clear(); } '<' => { parentheses_count += 1; current_part.push(c); } '>' => { parentheses_count -= 1; current_part.push(c); } _ => { current_part.push(c); } } } if !current_part.is_empty() { parts.push(current_part); } parts.into_iter() } impl From<&str> for CppType { fn from(value: &str) -> Self { let value = value.trim(); match value.split_once('<') { None => CppType { path: CppPath::from(value), generic_args: vec![], }, Some((path, generics)) => { let generics = generics.strip_suffix('>').unwrap(); CppType { path: CppPath::from(path), generic_args: split_string(generics).map(|x| CppType::from(&*x)).collect(), } } } } } // pub(crate) just for migration pub(crate) struct State { pub(crate) text: String, pub(crate) panic_to_exception: bool, } impl State { fn remove_no_except_in_panic(&mut self) { if self.panic_to_exception { self.text = self.text.replace(" noexcept ", " "); } } } impl Write for State { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.text += s; Ok(()) } } #[derive(Debug)] pub struct CppTraitMethod { pub name: String, pub rust_link_name: String, pub inputs: Vec, pub output: CppType, } impl CppTraitMethod { pub fn render_inputs(&self, namespace: &str) -> String { self.inputs .iter() .enumerate() .map(|(n, ty)| { format!( "::{}::__zngur_internal_move_from_rust< {ty} >(i{n})", namespace ) }) .join(", ") } } #[derive(Debug)] pub struct CppFnSig { pub rust_link_name: String, pub inputs: Vec, pub output: CppType, } impl CppFnSig { pub fn render_inputs(&self, namespace: &str) -> String { self.inputs .iter() .enumerate() .map(|(n, ty)| { format!( "::{}::__zngur_internal_move_from_rust< {ty} >(i{n})", namespace ) }) .join(", ") } } pub struct CppFnDefinition { pub name: CppPath, pub sig: CppFnSig, } pub struct CppExportedFnDefinition { pub name: String, pub sig: CppFnSig, } pub struct CppExportedImplDefinition { pub tr: Option, pub ty: CppType, pub methods: Vec<(String, CppFnSig)>, } impl CppExportedImplDefinition { pub fn render_tr(&self, namespace: &str) -> String { match &self.tr { Some(x) => format!("{x}"), None => format!("::{}::Inherent", namespace), } } } #[derive(Debug)] pub struct CppMethod { pub name: String, pub kind: ZngurMethodReceiver, pub sig: CppFnSig, } impl CppMethod { pub fn is_valid_field_method(&self, field_kind: &str) -> bool { if let zngur_def::ZngurMethodReceiver::Ref(m) = &self.kind { !(*m == zngur_def::Mutability::Mut && field_kind == "FieldRef") } else { false } } pub fn is_ref_not_mut(&self) -> bool { matches!( self.kind, zngur_def::ZngurMethodReceiver::Ref(zngur_def::Mutability::Not) ) } pub fn fn_name(&self, type_name: &str) -> String { format!("{}::{}", type_name, self.name) } pub fn render_sig_inputs_skip_one(&self) -> String { use itertools::Itertools; self.sig .inputs .iter() .skip(1) .enumerate() .map(|(n, ty)| format!("{ty} i{n}")) .join(", ") } } #[derive(Debug)] pub enum CppTraitDefinition { Fn { sig: CppFnSig, }, Normal { as_ty: CppType, methods: Vec, link_name: String, link_name_ref: String, }, } impl CppTraitDefinition { pub fn is_normal(&self) -> bool { matches!(self, CppTraitDefinition::Normal { .. }) } pub fn as_normal(&self) -> Option<(&CppType, &[CppTraitMethod], &str, &str)> { if let CppTraitDefinition::Normal { as_ty, methods, link_name, link_name_ref, } = self { Some((as_ty, methods, link_name, link_name_ref)) } else { None } } pub fn as_fn(&self) -> Option<&CppFnSig> { if let CppTraitDefinition::Fn { sig } = self { Some(sig) } else { None } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum CppLayoutPolicy { StackAllocated { size: usize, align: usize, }, HeapAllocated { size_fn: String, alloc_fn: String, free_fn: String, }, OnlyByRef, } impl CppLayoutPolicy { pub fn is_only_by_ref(&self) -> bool { matches!(self, CppLayoutPolicy::OnlyByRef) } pub fn is_stack_allocated(&self) -> bool { matches!(self, CppLayoutPolicy::StackAllocated { .. }) } pub fn stack_size(&self) -> usize { if let CppLayoutPolicy::StackAllocated { size, .. } = self { *size } else { 0 } } pub fn stack_align(&self) -> usize { if let CppLayoutPolicy::StackAllocated { align, .. } = self { *align } else { 1 } } pub fn alloc_heap(&self) -> String { if let CppLayoutPolicy::HeapAllocated { alloc_fn, .. } = self { format!("__zngur_data = {}();", alloc_fn) } else { "".to_owned() } } pub fn free_heap(&self) -> String { if let CppLayoutPolicy::HeapAllocated { free_fn, .. } = self { format!("{}(__zngur_data);", free_fn) } else { "".to_owned() } } pub fn copy_data(&self) -> String { if let CppLayoutPolicy::HeapAllocated { size_fn, .. } = self { format!( "memcpy(this->__zngur_data, other.__zngur_data, {}());", size_fn ) } else { "this->__zngur_data = other.__zngur_data;".to_owned() } } pub fn size_fn(&self) -> String { if let CppLayoutPolicy::HeapAllocated { size_fn, .. } = self { size_fn.clone() } else { "".to_owned() } } pub fn alloc_fn(&self) -> String { if let CppLayoutPolicy::HeapAllocated { alloc_fn, .. } = self { alloc_fn.clone() } else { "".to_owned() } } pub fn free_fn(&self) -> String { if let CppLayoutPolicy::HeapAllocated { free_fn, .. } = self { free_fn.clone() } else { "".to_owned() } } } #[derive(Debug)] pub struct CppTypeDefinition { pub ty: CppType, pub layout: CppLayoutPolicy, pub methods: Vec, pub constructors: Vec, pub fields: Vec, pub from_trait: Option, pub from_trait_ref: Option, pub wellknown_traits: Vec, pub cpp_value: Option, pub cpp_ref: Option, } impl CppTypeDefinition { pub fn has_unsized(&self) -> bool { self.wellknown_traits .iter() .any(|t| matches!(t, ZngurWellknownTraitData::Unsized)) } pub fn has_copy(&self) -> bool { self.wellknown_traits .iter() .any(|t| matches!(t, ZngurWellknownTraitData::Copy)) } pub fn drop_in_place(&self) -> String { self.wellknown_traits .iter() .find_map(|t| { if let ZngurWellknownTraitData::Drop { drop_in_place } = t { Some(drop_in_place.clone()) } else { None } }) .unwrap_or_default() } pub fn render_make_box(&self, name: &str, namespace: &str, crate_name: &str) -> String { use crate::rust::IntoCpp; use itertools::Itertools; match &self.from_trait { Some(RustTrait::Fn { inputs, output, .. }) => { let out_ty = output.into_cpp(namespace, crate_name); let in_tys = inputs .iter() .map(|x| x.into_cpp(namespace, crate_name)) .join(", "); format!( "static inline {} make_box(::std::function<{} ({})> f);", name, out_ty, in_tys ) } Some(RustTrait::Normal(_)) => { format!( "template\nstatic inline {} make_box(Args&&... args);", name ) } None => "".to_owned(), } } pub fn render_make_box_ref(&self, name: &str, namespace: &str, crate_name: &str) -> String { use crate::rust::IntoCpp; use itertools::Itertools; match &self.from_trait_ref { Some(RustTrait::Fn { inputs, output, .. }) => { let out_ty = output.into_cpp(namespace, crate_name); let in_tys = inputs .iter() .map(|x| x.into_cpp(namespace, crate_name)) .join(", "); format!( "inline {}(::std::function<{} ({})> f);", name, out_ty, in_tys ) } Some(tr @ RustTrait::Normal(_)) => { format!( "inline RefMut({}& arg);", tr.into_cpp(namespace, crate_name) ) } None => "".to_owned(), } } pub fn render_make_box_ref_only( &self, name: &str, namespace: &str, crate_name: &str, ) -> String { use crate::rust::IntoCpp; use itertools::Itertools; match &self.from_trait_ref { Some(RustTrait::Fn { inputs, output, .. }) => { let out_ty = output.into_cpp(namespace, crate_name); let in_tys = inputs .iter() .map(|x| x.into_cpp(namespace, crate_name)) .join(", "); format!( "inline {}(::std::function<{} ({})> f);", name, out_ty, in_tys ) } Some(tr @ RustTrait::Normal(_)) => { format!("inline Ref({}& arg);", tr.into_cpp(namespace, crate_name)) } None => "".to_owned(), } } } impl Default for CppTypeDefinition { fn default() -> Self { Self { ty: CppType::from("fill::me::you::forgot::it"), layout: CppLayoutPolicy::OnlyByRef, methods: vec![], constructors: vec![], fields: vec![], wellknown_traits: vec![], from_trait: None, from_trait_ref: None, cpp_value: None, cpp_ref: None, } } } #[derive(Default)] pub struct CppFile { pub header_file_name: String, pub type_defs: Vec, pub trait_defs: IndexMap, pub fn_defs: Vec, pub exported_fn_defs: Vec, pub exported_impls: Vec, pub additional_includes: String, pub panic_to_exception: bool, pub rust_cfg_defines: Vec, pub zng_header_in_place: bool, } impl CppFile { fn emit_h_file( &self, state: &mut State, namespace: &str, crate_name: &str, ) -> std::fmt::Result { let template = CppHeaderTemplate { panic_to_exception: self.panic_to_exception, additional_includes: &self.additional_includes, fn_deps: &self.fn_defs, type_defs: &self.type_defs, trait_defs: &self.trait_defs, exported_impls: &self.exported_impls, exported_fn_defs: &self.exported_fn_defs, rust_cfg_defines: &self.rust_cfg_defines, zng_header_in_place: self.zng_header_in_place, namespace, crate_name, }; state.text += normalize_whitespace(template.render().unwrap().as_str()).as_str(); Ok(()) } fn emit_cpp_file( &self, state: &mut State, is_really_needed: &mut bool, namespace: &str, ) -> std::fmt::Result { let template = CppSourceTemplate { header_file_name: &self.header_file_name, trait_defs: &self.trait_defs, exported_fn_defs: &self.exported_fn_defs, exported_impls: &self.exported_impls, cpp_namespace: namespace, }; state.text += normalize_whitespace(template.render().unwrap().as_str()).as_str(); *is_really_needed = !self.trait_defs.is_empty() || !self.exported_fn_defs.is_empty() || !self.exported_impls.is_empty(); Ok(()) } pub fn render(self, namespace: &str, crate_name: &str) -> (String, Option) { let mut h_file = State { text: "".to_owned(), panic_to_exception: self.panic_to_exception, }; let mut cpp_file = State { text: "".to_owned(), panic_to_exception: self.panic_to_exception, }; self.emit_h_file(&mut h_file, namespace, crate_name) .unwrap(); let mut is_cpp_needed = false; self.emit_cpp_file(&mut cpp_file, &mut is_cpp_needed, namespace) .unwrap(); h_file.remove_no_except_in_panic(); (h_file.text, is_cpp_needed.then_some(cpp_file.text)) } } pub fn cpp_handle_keyword(name: &str) -> &str { match name { "new" => "new_", "default" => "default_", x => x, } } pub fn cpp_handle_field_name(name: &str) -> String { if name.parse::().is_ok() { return format!("f{name}"); } cpp_handle_keyword(name).to_owned() } trait CountCharMatchesExt { fn count_start_matches(&self, pred: impl Fn(char) -> bool) -> usize; fn count_matches(&self, pred: impl Fn(char) -> bool) -> usize; } impl CountCharMatchesExt for str { fn count_start_matches(&self, pred: impl Fn(char) -> bool) -> usize { let mut count = 0; for c in self.chars() { if pred(c) { count += 1; } else { break; } } count } fn count_matches(&self, pred: impl Fn(char) -> bool) -> usize { let mut count = 0; for c in self.chars() { if pred(c) { count += 1; } } count } } /// Normalize newlines and indents in cpp source code /// /// A relatively naive algorithm intended only to correct excessive indentation and condense /// unneeded newlines that result from the sailfish template rendering. pub fn normalize_whitespace(cpp: &str) -> String { enum LineType { PreProc, NamespaceOpen, ExternOpen, BlockOpen, BlockClose, Statement, Unknown, Empty, } struct FormatState { indent: usize, last_indent: usize, last_line: LineType, } let mut state = FormatState { indent: 0, last_indent: 0, last_line: LineType::Empty, }; let lines = cpp.lines(); // builds vec of &str then `join`s them to reduce memory footprint and copies let mut out: Vec<&str> = Vec::new(); fn count_open_pairs(line: &str, pairs: &[[char; 2]]) -> isize { let mut count: isize = 0; for [open, close] in pairs { count += line.count_matches(|c| c == *open) as isize; count -= line.count_matches(|c| c == *close) as isize; } count } fn trim_end_c_comments(line: &str) -> &str { let mut j: usize = 0; let mut prev = false; for (index, c) in line.char_indices() { let is_slash = c == '/'; if is_slash && prev { // consecutive `//` means rest of line is a comment break; } prev = is_slash; j = index; } if j + 1 >= line.len() { j = line.len() } // SAFETY: `line.char_indices` returns valid indices unsafe { line.get_unchecked(0..j) } } fn line_type(line: &str) -> LineType { if line.trim().is_empty() { LineType::Empty } else if line.trim_start().starts_with('#') { LineType::PreProc } else if line.trim_start().starts_with("namespace") && line.ends_with('{') { LineType::NamespaceOpen } else if line.trim_start().starts_with("extern") && line.ends_with('{') { LineType::ExternOpen } else if line.ends_with('{') && count_open_pairs(line, &[['{', '}']]) > 0 { LineType::BlockOpen } else if (line.ends_with('}') || line.ends_with("};")) && count_open_pairs(line, &[['{', '}']]) < 0 { LineType::BlockClose } else if line.ends_with(';') { LineType::Statement } else { LineType::Unknown } } let mut last_indent: usize = 0; let mut last_line: &str = ""; let mut last_extra_indent: usize = 0; let mut indents: Vec = Vec::new(); for line in lines { let trimmed = trim_end_c_comments(line).trim_end(); let ty = line_type(trimmed); let indent = line.count_start_matches(char::is_whitespace); let mut emit_line = false; // don't indent this line let mut do_not_indent = false; // extra indent levels for this line let mut extra_indent = 0; // subtracted from indent let mut special_indent = 0; // when opening a indent level, don't indent this line let mut use_last_indent = false; match ty { LineType::PreProc => { do_not_indent = true; emit_line = true; } LineType::NamespaceOpen => { indents.clear(); state.indent = 0; state.last_indent = 0; emit_line = true; } LineType::ExternOpen => { indents.clear(); indents.push(4); state.indent = 4; state.last_indent = 0; emit_line = true; use_last_indent = true; } LineType::BlockOpen => { emit_line = true; indents.push(4); state.indent += 4; use_last_indent = true; } LineType::BlockClose => { emit_line = true; if let Some(n) = indents.pop() { state.indent = state.indent.saturating_sub(n); } } LineType::Statement | LineType::Unknown => { let open_pairs = count_open_pairs(last_line, &[['[', ']'], ['{', '}'], ['<', '>'], ['(', ')']]); if trimmed.ends_with("public:") || trimmed.ends_with("private:") { special_indent = 2; } else if indent == last_indent { extra_indent = last_extra_indent; } else if !matches!( state.last_line, LineType::BlockOpen | LineType::NamespaceOpen | LineType::ExternOpen ) && indent > last_indent && open_pairs > 0 { extra_indent += 4; } else if extra_indent > 0 && !matches!( state.last_line, LineType::BlockOpen | LineType::NamespaceOpen | LineType::ExternOpen ) && open_pairs < 0 { extra_indent = extra_indent.saturating_sub(4); } emit_line = true; } LineType::Empty => match &state.last_line { // preserve empty line if previous line held meaning LineType::Statement | LineType::Unknown | LineType::BlockClose => { do_not_indent = false; if !last_line.ends_with([',', '[', '<', '(']) { emit_line = true } } // eliminate all other empty lines _ => {} }, } state.last_line = ty; last_line = trimmed; last_indent = indent; last_extra_indent = extra_indent; if emit_line { // emit indent // extra_indent is for *this* line and forward // special_indent is for *this* line only if !do_not_indent && (((use_last_indent && state.last_indent > 0) || (!use_last_indent && state.indent > 0)) || extra_indent > 0 || special_indent > 0) { out.extend( iter::once(" ").cycle().take( (if use_last_indent { state.last_indent } else { state.indent } + extra_indent) .saturating_sub(special_indent), ), ); } // emit line without it's source indent out.push(line.trim()); out.push("\n"); } state.last_indent = state.indent; } out.join("") } ================================================ FILE: zngur-generator/src/lib.rs ================================================ use cpp::CppExportedFnDefinition; use cpp::CppExportedImplDefinition; use cpp::CppFile; use cpp::CppFnDefinition; use cpp::CppFnSig; use cpp::CppMethod; use cpp::CppPath; use cpp::CppTraitDefinition; use cpp::CppType; use cpp::CppTypeDefinition; use cpp::cpp_handle_keyword; use indexmap::map::Entry; use itertools::Itertools; use rust::IntoCpp; pub mod cpp; mod rust; mod template; use askama::Template; pub use rust::RustFile; pub use zngur_parser::{ParseResult, ParsedZngFile, cfg}; pub use zngur_def::*; use crate::template::ZngHeaderTemplate; pub struct ZngurGenerator(pub ZngurSpec, pub String); impl ZngurGenerator { pub fn build_from_zng(zng: ZngurSpec, crate_name: String) -> Self { ZngurGenerator(zng, crate_name) } pub fn render(self, zng_header_in_place: bool) -> (String, String, Option) { let zng = self.0; let mut cpp_file = CppFile::default(); cpp_file.header_file_name = zng.cpp_include_header_name.clone(); cpp_file.additional_includes = zng.additional_includes.0; cpp_file.zng_header_in_place = zng_header_in_place; for module in &zng.imported_modules { cpp_file .additional_includes .push_str(&format!("\n#include \"{}.h\"", module.path.display())); } let default_ns = zng.cpp_namespace.as_deref().unwrap_or("rust"); let sanitized_crate_name = self.1.replace('-', "_"); let mut rust_file = RustFile::new(&zng.mangling_base); rust_file.panic_to_exception = zng.convert_panic_to_exception.0; cpp_file.trait_defs = zng .traits .iter() .map(|(key, value)| { ( key.clone(), rust_file.add_builder_for_dyn_trait(value, default_ns, &sanitized_crate_name), ) }) .collect(); cpp_file.panic_to_exception = zng.convert_panic_to_exception.0; cpp_file .rust_cfg_defines .extend(zng.rust_cfg.iter().map(|(key, value)| { format!( "ZNGUR_CFG_{}{}", key.to_uppercase(), value .as_ref() .and_then(|value| if value.trim().is_empty() { None } else { Some(format!( "_{}", value .chars() .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) .collect::() .to_uppercase() )) }) .unwrap_or_default() ) })); for ty_def in zng.types { let ty = &ty_def.ty; let is_copy = ty_def.wellknown_traits.contains(&ZngurWellknownTrait::Copy); match ty_def.layout { LayoutPolicy::StackAllocated { size, align } => { rust_file.add_static_size_assert(&ty, size); rust_file.add_static_align_assert(&ty, align); } LayoutPolicy::Conservative { size, align } => { rust_file.add_static_size_upper_bound_assert(&ty, size); rust_file.add_static_align_upper_bound_assert(&ty, align); } LayoutPolicy::HeapAllocated => (), LayoutPolicy::OnlyByRef => (), } if is_copy { rust_file.add_static_is_copy_assert(&ty); } let mut cpp_methods = vec![]; let mut constructors = vec![]; let mut fields = vec![]; let mut wellknown_traits = vec![]; for constructor in ty_def.constructors { match constructor.name { Some(name) => { let rust_link_names = rust_file .add_constructor(&format!("{}::{}", ty, name), &constructor.inputs); cpp_methods.push(CppMethod { name: cpp_handle_keyword(&name).to_owned(), kind: ZngurMethodReceiver::Static, sig: CppFnSig { rust_link_name: rust_link_names.constructor, inputs: constructor .inputs .iter() .map(|x| x.1.into_cpp(default_ns, &sanitized_crate_name)) .collect(), output: ty.into_cpp(default_ns, &sanitized_crate_name), }, }); cpp_methods.push(CppMethod { name: format!("matches_{}", name), kind: ZngurMethodReceiver::Ref(Mutability::Not), sig: CppFnSig { rust_link_name: rust_link_names.match_check, inputs: vec![ ty.into_cpp(default_ns, &sanitized_crate_name).into_ref(), ], output: CppType::from("uint8_t"), }, }); } None => { let rust_link_name = rust_file .add_constructor(&format!("{}", ty), &constructor.inputs) .constructor; constructors.push(CppFnSig { rust_link_name, inputs: constructor .inputs .iter() .map(|x| x.1.into_cpp(default_ns, &sanitized_crate_name)) .collect(), output: ty.into_cpp(default_ns, &sanitized_crate_name), }); } } } for field in ty_def.fields { let extern_mn = rust_file.add_field_assertions(&field, &ty_def.ty); let field = ZngurFieldData { name: field.name, ty: field.ty, offset: match field.offset { Some(offset) => ZngurFieldDataOffset::Offset(offset), None => ZngurFieldDataOffset::Auto( extern_mn.expect("auto offset did not provide extern name"), ), }, }; fields.push(field); } if let RustType::Tuple(fields) = &ty_def.ty { if !fields.is_empty() { let rust_link_name = rust_file.add_tuple_constructor(&fields); constructors.push(CppFnSig { rust_link_name, inputs: fields .iter() .map(|x| x.into_cpp(default_ns, &sanitized_crate_name)) .collect(), output: ty.into_cpp(default_ns, &sanitized_crate_name), }); } } let is_unsized = ty_def .wellknown_traits .contains(&ZngurWellknownTrait::Unsized); for wellknown_trait in ty_def.wellknown_traits { let data = rust_file.add_wellknown_trait(&ty, wellknown_trait, is_unsized); wellknown_traits.push(data); } for method_details in ty_def.methods { let ZngurMethodDetails { data: method, use_path, deref, } = method_details; let rusty_inputs = real_inputs_of_method(&method, &ty); let sig = rust_file.add_function( &format!( "<{}>::{}::<{}>", deref.as_ref().map(|x| &x.0).unwrap_or(&ty), method.name, method.generics.iter().join(", "), ), &rusty_inputs, &method.output, use_path, deref.map(|x| x.1), default_ns, &sanitized_crate_name, ); cpp_methods.push(CppMethod { name: cpp_handle_keyword(&method.name).to_owned(), kind: method.receiver, sig, }); } cpp_file.type_defs.push(CppTypeDefinition { ty: ty.into_cpp(default_ns, &sanitized_crate_name), layout: rust_file.add_layout_policy_shim(&ty, ty_def.layout), constructors, fields, methods: cpp_methods, wellknown_traits, cpp_value: ty_def.cpp_value.map(|mut cpp_value| { cpp_value.0 = rust_file.add_cpp_value_bridge(&ty, &cpp_value.0); cpp_value }), cpp_ref: ty_def.cpp_ref, from_trait: if let RustType::Boxed(b) = &ty { if let RustType::Dyn(tr, _) = b.as_ref() { if let RustTrait::Fn { name, inputs, output, } = tr { if let Entry::Vacant(e) = cpp_file.trait_defs.entry(tr.clone()) { let rust_link_name = rust_file.add_builder_for_dyn_fn(name, inputs, output); e.insert(CppTraitDefinition::Fn { sig: CppFnSig { rust_link_name, inputs: inputs .iter() .map(|x| x.into_cpp(default_ns, &sanitized_crate_name)) .collect(), output: output.into_cpp(default_ns, &sanitized_crate_name), }, }); } } Some(tr.clone()) } else { None } } else { None }, from_trait_ref: if let RustType::Dyn(tr, _) = &ty { Some(tr.clone()) } else { None }, }); } for func in zng.funcs { let sig = rust_file.add_function( &func.path.to_string(), &func.inputs, &func.output, None, None, default_ns, &sanitized_crate_name, ); cpp_file.fn_defs.push(CppFnDefinition { name: CppPath::from_rust_path(&func.path.path, default_ns, &sanitized_crate_name), sig, }); } for func in zng.extern_cpp_funcs { let rust_link_name = rust_file.add_extern_cpp_function(&func.name, &func.inputs, &func.output); cpp_file.exported_fn_defs.push(CppExportedFnDefinition { name: func.name.clone(), sig: CppFnSig { rust_link_name, inputs: func .inputs .into_iter() .map(|x| x.into_cpp(default_ns, &sanitized_crate_name)) .collect(), output: func.output.into_cpp(default_ns, &sanitized_crate_name), }, }); } for impl_block in zng.extern_cpp_impls { let rust_link_names = rust_file.add_extern_cpp_impl( &impl_block.ty, impl_block.tr.as_ref(), &impl_block.methods, ); cpp_file.exported_impls.push(CppExportedImplDefinition { tr: impl_block .tr .map(|x| x.into_cpp(default_ns, &sanitized_crate_name)), ty: impl_block.ty.into_cpp(default_ns, &sanitized_crate_name), methods: impl_block .methods .iter() .zip(&rust_link_names) .map(|(method, link_name)| { let inputs = real_inputs_of_method(method, &impl_block.ty); let inputs = inputs .iter() .map(|ty| ty.into_cpp(default_ns, &sanitized_crate_name)) .collect(); ( cpp_handle_keyword(&method.name).to_owned(), CppFnSig { rust_link_name: link_name.clone(), inputs, output: method.output.into_cpp(default_ns, &sanitized_crate_name), }, ) }) .collect(), }); } let (h, cpp) = cpp_file.render(default_ns, &sanitized_crate_name); (rust_file.text, h, cpp) } } pub struct ZngHeaderGenerator { pub panic_to_exception: bool, pub cpp_namespace: String, } impl ZngHeaderGenerator { /// Renders the zngur.h header pub fn render(&self) -> String { let zng_h = ZngHeaderTemplate { panic_to_exception: self.panic_to_exception, cpp_namespace: self.cpp_namespace.clone(), }; zng_h.render().unwrap() } } fn real_inputs_of_method(method: &ZngurMethod, ty: &RustType) -> Vec { let receiver_type = match method.receiver { ZngurMethodReceiver::Static => None, ZngurMethodReceiver::Ref(m) => Some(RustType::Ref(m, Box::new(ty.clone()))), ZngurMethodReceiver::Move => Some(ty.clone()), }; let rusty_inputs = receiver_type .into_iter() .chain(method.inputs.clone()) .collect::>(); rusty_inputs } ================================================ FILE: zngur-generator/src/rust.rs ================================================ use std::fmt::Write; use itertools::Itertools; use sha2::{Digest, Sha256}; use crate::{ ZngurTrait, ZngurWellknownTrait, ZngurWellknownTraitData, cpp::{CppFnSig, CppLayoutPolicy, CppPath, CppTraitDefinition, CppTraitMethod, CppType}, }; use zngur_def::*; pub trait IntoCpp { fn into_cpp(&self, namespace: &str, crate_name: &str) -> CppType; } impl IntoCpp for RustPathAndGenerics { fn into_cpp(&self, namespace: &str, crate_name: &str) -> CppType { let RustPathAndGenerics { path, generics, named_generics, } = self; let named_generics = named_generics.iter().sorted_by_key(|x| &x.0).map(|x| &x.1); CppType { path: CppPath::from_rust_path(path, namespace, crate_name), generic_args: generics .iter() .chain(named_generics) .map(|x| x.into_cpp(namespace, crate_name)) .collect(), } } } impl IntoCpp for RustTrait { fn into_cpp(&self, namespace: &str, crate_name: &str) -> CppType { match self { RustTrait::Normal(pg) => pg.into_cpp(namespace, crate_name), RustTrait::Fn { name, inputs, output, } => CppType { path: CppPath::from(&*format!("{namespace}::{name}")), generic_args: inputs .iter() .chain(Some(&**output)) .map(|x| x.into_cpp(namespace, crate_name)) .collect(), }, } } } impl IntoCpp for RustType { fn into_cpp(&self, namespace: &str, crate_name: &str) -> CppType { fn for_builtin(this: &RustType, namespace: &str, crate_name: &str) -> Option { match this { RustType::Primitive(s) => match s { PrimitiveRustType::Uint(s) => Some(CppType::from(&*format!("uint{s}_t"))), PrimitiveRustType::Int(s) => Some(CppType::from(&*format!("int{s}_t"))), PrimitiveRustType::Float(32) => Some(CppType::from("float_t")), PrimitiveRustType::Float(64) => Some(CppType::from("double_t")), PrimitiveRustType::Float(_) => unreachable!(), PrimitiveRustType::Usize => Some(CppType::from("size_t")), PrimitiveRustType::Bool | PrimitiveRustType::Str | PrimitiveRustType::Char => { None } PrimitiveRustType::ZngurCppOpaqueOwnedObject => Some(CppType::from(&*format!( "{namespace}::ZngurCppOpaqueOwnedObject" ))), }, RustType::Raw(Mutability::Mut, t) => Some(CppType::from(&*format!( "{}*", for_builtin(t, namespace, crate_name)? .to_string() .strip_prefix("::")? ))), RustType::Raw(Mutability::Not, t) => Some(CppType::from(&*format!( "{} const*", for_builtin(t, namespace, crate_name)? .to_string() .strip_prefix("::")? ))), _ => None, } } if let Some(builtin) = for_builtin(self, namespace, crate_name) { return builtin; } match self { RustType::Primitive(s) => match s { PrimitiveRustType::Bool => CppType::from(&*format!("{namespace}::Bool")), PrimitiveRustType::Str => CppType::from(&*format!("{namespace}::Str")), PrimitiveRustType::Char => CppType::from(&*format!("{namespace}::Char")), _ => unreachable!(), }, RustType::Boxed(t) => CppType { path: CppPath::from(&*format!("{namespace}::Box")), generic_args: vec![t.into_cpp(namespace, crate_name)], }, RustType::Ref(m, t) => CppType { path: match m { Mutability::Mut => CppPath::from(&*format!("{}::RefMut", namespace)), Mutability::Not => CppPath::from(&*format!("{}::Ref", namespace)), }, generic_args: vec![t.into_cpp(namespace, crate_name)], }, RustType::Slice(s) => CppType { path: CppPath::from(&*format!("{namespace}::Slice")), generic_args: vec![s.into_cpp(namespace, crate_name)], }, RustType::Raw(m, t) => CppType { path: match m { Mutability::Mut => CppPath::from(&*format!("{namespace}::RawMut")), Mutability::Not => CppPath::from(&*format!("{namespace}::Raw")), }, generic_args: vec![t.into_cpp(namespace, crate_name)], }, RustType::Adt(pg) => pg.into_cpp(namespace, crate_name), RustType::Tuple(v) => { if v.is_empty() { return CppType::from(&*format!("{namespace}::Unit")); } CppType { path: CppPath::from(&*format!("{namespace}::Tuple")), generic_args: v .into_iter() .map(|x| x.into_cpp(namespace, crate_name)) .collect(), } } RustType::Dyn(tr, marker_bounds) => { let tr_as_cpp_type = tr.into_cpp(namespace, crate_name); CppType { path: CppPath::from(&*format!("{namespace}::Dyn")), generic_args: [tr_as_cpp_type] .into_iter() .chain( marker_bounds .iter() .map(|x| CppType::from(&*format!("{namespace}::{x}"))), ) .collect(), } } RustType::Impl(_, _) => panic!("impl Trait is invalid in C++"), } } } pub struct RustFile { pub text: String, pub panic_to_exception: bool, pub mangling_base: String, } impl RustFile { pub fn new(mangling_base: &str) -> Self { Self { text: r#" #[allow(dead_code)] mod zngur_types { pub struct ZngurCppOpaqueBorrowedObject(()); #[repr(C)] pub struct ZngurCppOpaqueOwnedObject { data: *mut u8, destructor: extern "C" fn(*mut u8), } impl ZngurCppOpaqueOwnedObject { pub unsafe fn new( data: *mut u8, destructor: extern "C" fn(*mut u8), ) -> Self { Self { data, destructor } } pub fn ptr(&self) -> *mut u8 { self.data } } impl Drop for ZngurCppOpaqueOwnedObject { fn drop(&mut self) { (self.destructor)(self.data) } } } #[allow(unused_imports)] pub use zngur_types::ZngurCppOpaqueOwnedObject; #[allow(unused_imports)] pub use zngur_types::ZngurCppOpaqueBorrowedObject; macro_rules! __zngur_str_as_array { ($s:expr) => {{ const VAL: &str = $s; // SAFETY: `VAL` has at least size `N` because it's const len is right there. const ARR: [u8; VAL.len()] = unsafe { *(VAL.as_bytes() as *const [u8]).cast() }; ARR }}; } pub const fn __zngur_usize_num_digits(val: usize) -> usize { // docs currently say 64bit only but that's a bug if val == 0 { 1 } else { val.ilog10() as usize + 1 } } pub const fn __zngur_usize_digit(val: usize, digit: usize) -> u8 { let mut temp = val; let mut i = 0; while i < digit { temp /= 10; i += 1; } if temp == 0 && val > 0 { ::core::panic!("no such digit!") } else { (temp % 10) as u8 } } pub const fn __zngur_digit_to_ascii(digit: u8) -> u8 { ::core::assert!(digit <= 9); digit + b'0' } pub const fn __zngur_usize_to_digit_array(val: usize) -> [u8; N] { let mut arr: [u8; N] = [0; N]; let mut i = 0; while i < N { arr[N - 1 - i] = __zngur_digit_to_ascii(__zngur_usize_digit(val, i)); i += 1; } arr } macro_rules! __zngur_usize_to_str { ($x:expr) => {{ const VAL: usize = $x; const ARR: [u8; __zngur_usize_num_digits(VAL)] = __zngur_usize_to_digit_array(VAL); // SAFETY: `ARR` is an ascii byte array which is utf8 compliant const STR: &str = unsafe { str::from_utf8_unchecked(&ARR) }; STR }}; } pub const fn __zngur_const_str_array_concat( x: [u8; N], y: [u8; M], ) -> [u8; T] { ::core::assert!(N + M == T); let mut arr: [u8; T] = [0; T]; let mut i = 0; while i < N { arr[i] = x[i]; i += 1; } while i - N < M { arr[i] = y[i - N]; i += 1; } arr } macro_rules! __zngur_const_str_concat { ( $x:expr, $y:expr $(,)? ) => {{ const X: &str = $x; const Y: &str = $y; const LEN: usize = X.len() + Y.len(); const ARR: [u8; LEN] = __zngur_const_str_array_concat::( __zngur_str_as_array!(X), __zngur_str_as_array!(Y), ); // SAFETY: `ARR` is an concatenated utf8 byte array built from validated `const str&` const STR: &str = unsafe { str::from_utf8_unchecked(&ARR) }; STR }}; ( $x:expr, $y:expr, $($rest:expr),+ $(,)? ) => { __zngur_const_str_concat!($x, __zngur_const_str_concat!( $y, $($rest),+ )) }; } macro_rules! __zngur_assert_is_copy { ($x:ty $(,)?) => { const _: () = { const fn static_assert_is_copy() {} static_assert_is_copy::<$x>(); }; }; } macro_rules! __zngur_assert_size { ($x:ty, $size:expr $(,)?) => { const _: () = ::core::assert!( $size == ::core::mem::size_of::<$x>(), "{}", __zngur_const_str_concat!( "zngur declared size of ", stringify!($x), " is incorrect: expected ", __zngur_usize_to_str!($size), " , real size is ", __zngur_usize_to_str!(::core::mem::size_of::<$x>()), ) ); }; } macro_rules! __zngur_assert_align { ($x:ty, $align:expr $(,)?) => { const _: () = ::core::assert!( $align == ::core::mem::align_of::<$x>(), "{}", __zngur_const_str_concat!( "zngur declared align of ", stringify!($x), " is incorrect: expected ", __zngur_usize_to_str!($align), " , real align is ", __zngur_usize_to_str!(::core::mem::align_of::<$x>()), ) ); }; } macro_rules! __zngur_assert_size_conservative { ($x:ty, $size:expr $(,)?) => { const _: () = ::core::assert!( $size >= ::core::mem::size_of::<$x>(), "{}", __zngur_const_str_concat!( "zngur declared conservative size of ", stringify!($x), " is incorrect: expected size less than or equal to ", __zngur_usize_to_str!($size), " , real size is ", __zngur_usize_to_str!(::core::mem::size_of::<$x>()), ) ); }; } macro_rules! __zngur_assert_align_conservative { ($x:ty, $align:expr $(,)?) => { const _: () = ::core::assert!( $align >= ::core::mem::align_of::<$x>(), "{}", __zngur_const_str_concat!( "zngur declared conservative align of ", stringify!($x), " is incorrect: expected align less than or equal to ", __zngur_usize_to_str!($align), " , real align is ", __zngur_usize_to_str!(::core::mem::align_of::<$x>()), ) ); }; } macro_rules! __zngur_assert_has_field { ($x:ty, $y:ty, $($field:tt)+ $(,)?) => { const _: () = { #[allow(dead_code)] fn check_field(value: $x) -> $y { value.$($field)+ } }; }; } macro_rules! __zngur_assert_field_offset { ($x:ty, $offset:expr, $($field:tt)+ $(,)?) => { const _: () = ::core::assert!( $offset == ::core::mem::offset_of!($x, $($field)+), "{}", __zngur_const_str_concat!( "zngur declared offset of field ", stringify!($($field)+), " in ", stringify!($x), " is incorrect: expected offset of ", __zngur_usize_to_str!($offset), " , real offset is ", __zngur_usize_to_str!(::core::mem::offset_of!($x, $($field)+)), ) ); }; } "# .to_owned(), panic_to_exception: false, mangling_base: mangling_base.to_owned(), } } } impl Write for RustFile { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.text.write_str(s) } } macro_rules! w { ($dst:expr, $($arg:tt)*) => { { let _ = write!($dst, $($arg)*); } }; } macro_rules! wln { ($dst:expr, $($arg:tt)*) => { { let _ = writeln!($dst, $($arg)*); } }; } pub fn hash_of_sig(sig: &[RustType]) -> String { let mut text = "".to_owned(); for elem in sig { text += &format!("{elem}+"); } let digset = Sha256::digest(&text); hex::encode(&digset[..5]) } fn mangle_name(name: &str, mangling_base: &str) -> String { let mut name = "_zngur_" .chars() .chain(mangling_base.chars()) .chain(name.chars().filter(|c| !c.is_whitespace())) .chain(Some('_')) .collect::(); let bads = [ (1, "::<", 'm'), (1, ">::", 'n'), (1, "->", 'a'), (2, "&", 'r'), (2, "=", 'e'), (2, "<", 'x'), (2, ">", 'y'), (2, "[", 'j'), (2, "]", 'k'), (2, "::", 's'), (2, ",", 'c'), (2, "+", 'l'), (2, "(", 'p'), (2, ")", 'q'), (2, "@", 'z'), (2, "-", 'h'), ]; while let Some((pos, which)) = bads.iter().filter_map(|x| Some((name.find(x.1)?, x))).min() { name.replace_range(pos..pos + which.1.len(), "_"); w!(name, "{}{pos}", which.2); } name } pub struct ConstructorMangledNames { pub constructor: String, pub match_check: String, } impl RustFile { fn mangle_name(&self, name: &str) -> String { mangle_name(name, &self.mangling_base) } fn call_cpp_function(&mut self, name: &str, inputs: usize) { for n in 0..inputs { wln!(self, "let mut i{n} = ::core::mem::MaybeUninit::new(i{n});") } wln!(self, "let mut r = ::core::mem::MaybeUninit::uninit();"); w!(self, "{name}"); for n in 0..inputs { w!(self, "i{n}.as_mut_ptr() as *mut u8, "); } wln!(self, "r.as_mut_ptr() as *mut u8);"); wln!(self, "r.assume_init()"); } pub fn add_static_is_copy_assert(&mut self, ty: &RustType) { wln!(self, r#"__zngur_assert_is_copy!({ty});"#); } pub fn add_static_size_assert(&mut self, ty: &RustType, size: usize) { wln!(self, r#"__zngur_assert_size!({ty}, {size});"#); } pub fn add_static_align_assert(&mut self, ty: &RustType, align: usize) { wln!(self, r#"__zngur_assert_align!({ty}, {align});"#); } pub fn add_static_size_upper_bound_assert(&mut self, ty: &RustType, size: usize) { wln!(self, r#"__zngur_assert_size_conservative!({ty}, {size});"#); } pub fn add_static_align_upper_bound_assert(&mut self, ty: &RustType, align: usize) { wln!( self, r#"__zngur_assert_align_conservative!({ty}, {align});"# ); } pub(crate) fn add_builder_for_dyn_trait( &mut self, tr: &ZngurTrait, namespace: &str, crate_name: &str, ) -> CppTraitDefinition { assert!(matches!(tr.tr, RustTrait::Normal { .. })); let mut method_mangled_name = vec![]; wln!(self, r#"unsafe extern "C" {{"#); for method in &tr.methods { let name = self.mangle_name(&tr.tr.to_string()) + "_" + &method.name + "_" + &hash_of_sig(&method.generics) + "_" + &hash_of_sig(&method.inputs); wln!( self, r#"fn {name}(data: *mut u8, {} o: *mut u8);"#, method .inputs .iter() .enumerate() .map(|(n, _)| format!("i{n}: *mut u8,")) .join(" ") ); method_mangled_name.push(name); } wln!(self, "}}"); let link_name = self.add_builder_for_dyn_trait_owned(tr, &method_mangled_name); let link_name_ref = self.add_builder_for_dyn_trait_borrowed(tr, &method_mangled_name); CppTraitDefinition::Normal { as_ty: tr.tr.into_cpp(namespace, crate_name), methods: tr .methods .clone() .into_iter() .zip(method_mangled_name) .map(|(x, rust_link_name)| CppTraitMethod { name: x.name, rust_link_name, inputs: x .inputs .into_iter() .map(|x| x.into_cpp(namespace, crate_name)) .collect(), output: x.output.into_cpp(namespace, crate_name), }) .collect(), link_name, link_name_ref, } } fn add_builder_for_dyn_trait_owned( &mut self, tr: &ZngurTrait, method_mangled_name: &[String], ) -> String { let trait_name = tr.tr.to_string(); let (trait_without_assocs, assocs) = tr.tr.clone().take_assocs(); let mangled_name = self.mangle_name(&trait_name); wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {mangled_name}( data: *mut u8, destructor: extern "C" fn(*mut u8), o: *mut u8, ) {{ struct Wrapper {{ value: ZngurCppOpaqueOwnedObject, }} impl {trait_without_assocs} for Wrapper {{ "# ); for (name, ty) in assocs { wln!(self, " type {name} = {ty};"); } for (method, rust_link_name) in tr.methods.iter().zip(method_mangled_name) { w!(self, " fn {}(", method.name); match method.receiver { crate::ZngurMethodReceiver::Static => { panic!("traits with static methods are not object safe"); } crate::ZngurMethodReceiver::Ref(Mutability::Not) => w!(self, "&self"), crate::ZngurMethodReceiver::Ref(Mutability::Mut) => w!(self, "&mut self"), crate::ZngurMethodReceiver::Move => w!(self, "self"), } for (i, ty) in method.inputs.iter().enumerate() { w!(self, ", i{i}: {ty}"); } wln!(self, ") -> {} {{ unsafe {{", method.output); wln!(self, " let data = self.value.ptr();"); self.call_cpp_function(&format!("{rust_link_name}(data, "), method.inputs.len()); wln!(self, " }} }}"); } wln!( self, r#" }} unsafe {{ let this = Wrapper {{ value: ZngurCppOpaqueOwnedObject::new(data, destructor), }}; let r: Box = Box::new(this); std::ptr::write(o as *mut _, r) }} }}"# ); mangled_name } fn add_builder_for_dyn_trait_borrowed( &mut self, tr: &ZngurTrait, method_mangled_name: &[String], ) -> String { let trait_name = tr.tr.to_string(); let (trait_without_assocs, assocs) = tr.tr.clone().take_assocs(); let mangled_name = self.mangle_name(&trait_name) + "_borrowed"; wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {mangled_name}( data: *mut u8, o: *mut u8, ) {{ struct Wrapper(ZngurCppOpaqueBorrowedObject); impl {trait_without_assocs} for Wrapper {{ "# ); for (name, ty) in assocs { wln!(self, " type {name} = {ty};"); } for (method, rust_link_name) in tr.methods.iter().zip(method_mangled_name) { w!(self, " fn {}(", method.name); match method.receiver { crate::ZngurMethodReceiver::Static => { panic!("traits with static methods are not object safe"); } crate::ZngurMethodReceiver::Ref(Mutability::Not) => w!(self, "&self"), crate::ZngurMethodReceiver::Ref(Mutability::Mut) => w!(self, "&mut self"), crate::ZngurMethodReceiver::Move => w!(self, "self"), } for (i, ty) in method.inputs.iter().enumerate() { w!(self, ", i{i}: {ty}"); } wln!(self, ") -> {} {{ unsafe {{", method.output); wln!( self, " let data = ::std::mem::transmute::<_, *mut u8>(self);" ); self.call_cpp_function(&format!("{rust_link_name}(data, "), method.inputs.len()); wln!(self, " }} }}"); } wln!( self, r#" }} unsafe {{ let this = data as *mut Wrapper; let r: &dyn {trait_name} = &*this; std::ptr::write(o as *mut _, r) }} }}"# ); mangled_name } pub fn add_builder_for_dyn_fn( &mut self, name: &str, inputs: &[RustType], output: &RustType, ) -> String { let mangled_name = self.mangle_name(&inputs.iter().chain(Some(output)).join(", ")); let trait_str = format!("{name}({}) -> {output}", inputs.iter().join(", ")); wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {mangled_name}( data: *mut u8, destructor: extern "C" fn(*mut u8), call: extern "C" fn(data: *mut u8, {} o: *mut u8), o: *mut u8, ) {{ let this = unsafe {{ ZngurCppOpaqueOwnedObject::new(data, destructor) }}; let r: Box = Box::new(move |{}| unsafe {{ _ = &this; let data = this.ptr(); "#, inputs .iter() .enumerate() .map(|(n, _)| format!("i{n}: *mut u8, ")) .join(" "), inputs .iter() .enumerate() .map(|(n, ty)| format!("i{n}: {ty}")) .join(", "), ); self.call_cpp_function("call(data, ", inputs.len()); wln!( self, r#" }}); unsafe {{ std::ptr::write(o as *mut _, r) }} }}"# ); mangled_name } pub fn add_tuple_constructor(&mut self, fields: &[RustType]) -> String { let constructor = self.mangle_name(&fields.iter().join("&")); w!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {constructor}("# ); for name in 0..fields.len() { w!(self, "f_{name}: *mut u8, "); } w!( self, r#"o: *mut u8) {{ unsafe {{ ::std::ptr::write(o as *mut _, ("# ); for (name, ty) in fields.iter().enumerate() { w!(self, "::std::ptr::read(f_{name} as *mut {ty}), "); } wln!(self, ")) }} }}"); constructor } pub fn add_constructor( &mut self, rust_name: &str, args: &[(String, RustType)], ) -> ConstructorMangledNames { let constructor = self.mangle_name(rust_name); let match_check = format!("{constructor}_check"); w!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {constructor}("# ); for (name, _) in args { w!(self, "f_{name}: *mut u8, "); } w!( self, r#"o: *mut u8) {{ unsafe {{ ::std::ptr::write(o as *mut _, {rust_name} {{ "# ); for (name, ty) in args { w!(self, "{name}: ::std::ptr::read(f_{name} as *mut {ty}), "); } wln!(self, "}}) }} }}"); w!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {match_check}(i: *mut u8, o: *mut u8) {{ unsafe {{ *o = matches!(&*(i as *mut &_), {rust_name} {{ .. }}) as u8; }} }}"# ); ConstructorMangledNames { constructor, match_check, } } pub(crate) fn add_field_assertions( &mut self, field: &ZngurField, owner: &RustType, ) -> Option { let ZngurField { name, ty, offset } = field; wln!(self, r#"__zngur_assert_has_field!({owner}, {ty}, {name});"#); if let Some(offset) = offset { wln!( self, r#"__zngur_assert_field_offset!({owner}, {offset}, {name});"# ); None } else { let mn = self.mangle_name(&format!("{}_field_{}_offset", &owner, &name)); wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub static {mn}: usize = ::std::mem::offset_of!({owner}, {name}); "# ); Some(mn) } } pub fn add_extern_cpp_impl( &mut self, owner: &RustType, tr: Option<&RustTrait>, methods: &[ZngurMethod], ) -> Vec { let mut mangled_names = vec![]; w!(self, r#"unsafe extern "C" {{"#); for method in methods { let mn = self.mangle_name(&format!("{}_extern_method_{}", owner, method.name)); w!( self, r#" fn {mn}("# ); let input_offset = if method.receiver == ZngurMethodReceiver::Static { 0 } else { 1 }; for n in 0..method.inputs.len() + input_offset { w!(self, "i{n}: *mut u8, "); } wln!(self, r#"o: *mut u8);"#); mangled_names.push(mn); } w!(self, r#"}}"#); match tr { Some(tr) => { let (tr, assocs) = tr.clone().take_assocs(); w!(self, r#"impl {tr} for {owner} {{"#); for (name, ty) in assocs { w!(self, r#"type {name} = {ty};"#); } } None => w!(self, r#"impl {owner} {{"#), } for (mn, method) in mangled_names.iter().zip(methods) { if tr.is_none() { w!(self, "pub "); } w!(self, r#"fn {}("#, method.name); match method.receiver { ZngurMethodReceiver::Static => (), ZngurMethodReceiver::Ref(Mutability::Mut) => w!(self, "&mut self, "), ZngurMethodReceiver::Ref(Mutability::Not) => w!(self, "&self, "), ZngurMethodReceiver::Move => w!(self, "self, "), } let input_offset = if method.receiver == ZngurMethodReceiver::Static { 0 } else { 1 }; for (ty, n) in method.inputs.iter().zip(input_offset..) { w!(self, "i{n}: {ty}, "); } wln!(self, ") -> {} {{ unsafe {{", method.output); if method.receiver != ZngurMethodReceiver::Static { wln!(self, "let i0 = self;"); } self.call_cpp_function(&format!("{mn}("), method.inputs.len() + input_offset); wln!(self, "}} }}"); } w!(self, r#"}}"#); mangled_names } pub fn add_extern_cpp_function( &mut self, rust_name: &str, inputs: &[RustType], output: &RustType, ) -> String { let mangled_name = self.mangle_name(rust_name); w!( self, r#" unsafe extern "C" {{ fn {mangled_name}("# ); for (n, _) in inputs.iter().enumerate() { w!(self, "i{n}: *mut u8, "); } wln!(self, r#"o: *mut u8); }}"#); w!( self, r#" pub fn {rust_name}("# ); for (n, ty) in inputs.iter().enumerate() { w!(self, "i{n}: {ty}, "); } wln!(self, ") -> {output} {{ unsafe {{"); self.call_cpp_function(&format!("{mangled_name}("), inputs.len()); wln!(self, "}} }}"); mangled_name } pub fn add_cpp_value_bridge(&mut self, ty: &RustType, field: &str) -> String { let mangled_name = self.mangle_name(&format!("{ty}_cpp_value_{field}")); w!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {mangled_name}(d: *mut u8) -> *mut ZngurCppOpaqueOwnedObject {{ unsafe {{ &mut (*(d as *mut {ty})).{field} }} }}"# ); mangled_name } pub fn add_function( &mut self, rust_name: &str, inputs: &[RustType], output: &RustType, use_path: Option>, deref: Option, namespace: &str, crate_name: &str, ) -> CppFnSig { let mut mangled_name = self.mangle_name(rust_name) + "_" + &hash_of_sig(&inputs); if deref.is_some() { mangled_name += "_deref"; } w!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] #[allow(unused_parens)] pub extern "C" fn {mangled_name}("# ); for n in 0..inputs.len() { w!(self, "i{n}: *mut u8, "); } let (modified_output, is_impl_trait) = if let RustType::Impl(tr, bounds) = output { ( RustType::Boxed(Box::new(RustType::Dyn(tr.clone(), bounds.clone()))), true, ) } else { (output.clone(), false) }; wln!(self, "o: *mut u8) {{ unsafe {{"); self.wrap_in_catch_unwind(|this| { if let Some(use_path) = use_path { if use_path.first().is_some_and(|x| x == "crate") { wln!(this, " use {};", use_path.iter().join("::")); } else { wln!(this, " use ::{};", use_path.iter().join("::")); } } w!( this, " ::std::ptr::write(o as *mut {modified_output}, {impl_trait} {rust_name}(", impl_trait = if is_impl_trait { "Box::new( " } else { "" }, ); match deref { Some(Mutability::Mut) => w!(this, "::std::ops::DerefMut::deref_mut"), Some(Mutability::Not) => w!(this, "::std::ops::Deref::deref"), None => {} } for (n, ty) in inputs.iter().enumerate() { w!(this, "(::std::ptr::read(i{n} as *mut {ty})), "); } if is_impl_trait { wln!(this, ")));"); } else { wln!(this, "));"); } }); wln!(self, " }} }}"); CppFnSig { rust_link_name: mangled_name, inputs: inputs .iter() .map(|ty| ty.into_cpp(namespace, crate_name)) .collect(), output: modified_output.into_cpp(namespace, crate_name), } } pub(crate) fn add_wellknown_trait( &mut self, ty: &RustType, wellknown_trait: ZngurWellknownTrait, is_unsized: bool, ) -> ZngurWellknownTraitData { match wellknown_trait { ZngurWellknownTrait::Unsized => ZngurWellknownTraitData::Unsized, ZngurWellknownTrait::Copy => ZngurWellknownTraitData::Copy, ZngurWellknownTrait::Drop => { let drop_in_place = self.mangle_name(&format!("{ty}=drop_in_place")); wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {drop_in_place}(v: *mut u8) {{ unsafe {{ ::std::ptr::drop_in_place(v as *mut {ty}); }} }}"# ); ZngurWellknownTraitData::Drop { drop_in_place } } ZngurWellknownTrait::Debug => { let pretty_print = self.mangle_name(&format!("{ty}=debug_pretty")); let debug_print = self.mangle_name(&format!("{ty}=debug_print")); let dbg_ty = if !is_unsized { format!("{ty}") } else { format!("&{ty}") }; wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {pretty_print}(v: *mut u8) {{ eprintln!("{{:#?}}", unsafe {{ &*(v as *mut {dbg_ty}) }}); }}"# ); wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub extern "C" fn {debug_print}(v: *mut u8) {{ eprintln!("{{:?}}", unsafe {{ &*(v as *mut {dbg_ty}) }}); }}"# ); ZngurWellknownTraitData::Debug { pretty_print, debug_print, } } } } fn wrap_in_catch_unwind(&mut self, f: impl FnOnce(&mut RustFile)) { if !self.panic_to_exception { f(self); } else { wln!( self, r#"unsafe extern "C" {{ fn __zngur_mark_panicked(); }} let e = ::std::panic::catch_unwind(|| {{"# ); f(self); wln!(self, "}});"); wln!(self, "if let Err(_) = e {{ __zngur_mark_panicked(); }}"); } } pub(crate) fn add_layout_policy_shim( &mut self, ty: &RustType, layout: LayoutPolicy, ) -> CppLayoutPolicy { match layout { LayoutPolicy::StackAllocated { size, align } => { CppLayoutPolicy::StackAllocated { size, align } } LayoutPolicy::Conservative { size, align } => { CppLayoutPolicy::StackAllocated { size, align } } LayoutPolicy::HeapAllocated => { let size_fn = self.mangle_name(&format!("{ty}_size_fn")); let alloc_fn = self.mangle_name(&format!("{ty}_alloc_fn")); let free_fn = self.mangle_name(&format!("{ty}_free_fn")); wln!( self, r#" #[allow(non_snake_case)] #[unsafe(no_mangle)] pub fn {size_fn}() -> usize {{ ::std::mem::size_of::<{ty}>() }} #[allow(non_snake_case)] #[unsafe(no_mangle)] pub fn {alloc_fn}() -> *mut u8 {{ unsafe {{ ::std::alloc::alloc(::std::alloc::Layout::new::<{ty}>()) }} }} #[allow(non_snake_case)] #[unsafe(no_mangle)] pub fn {free_fn}(p: *mut u8) {{ unsafe {{ ::std::alloc::dealloc(p, ::std::alloc::Layout::new::<{ty}>()) }} }} "# ); CppLayoutPolicy::HeapAllocated { size_fn, alloc_fn, free_fn, } } LayoutPolicy::OnlyByRef => CppLayoutPolicy::OnlyByRef, } } } ================================================ FILE: zngur-generator/src/template.rs ================================================ use crate::cpp::{ CppExportedFnDefinition, CppExportedImplDefinition, CppFnDefinition, CppTraitDefinition, CppTypeDefinition, }; use askama::Template; use indexmap::IndexMap; use zngur_def::*; use zngur_def::{ZngurMethodReceiver, ZngurWellknownTraitData}; use crate::rust::IntoCpp; /// Macro for template string interpolation over iterables with enumeration /// /// Usage: /// - splat!(items, |idx, ty|, "{ty} param{idx}") /// - splat!(items.iter().skip(1), |n, t|, "{t} i{n}") /// - splat!(items, "{el} i{n}") // Default names: n (index), el (element) macro_rules! splat { // Closure-style with custom variable names ($inputs:expr, |$n:ident, $el:ident|, $pattern:literal $(, $format_vars:expr)* $(,)?) => {{ use itertools::Itertools; #[allow(unused_variables)] $inputs .into_iter() .enumerate() .map(|($n, $el)| format!($pattern $(, $format_vars)*)) .join(", ") }}; ($inputs:expr, |$n:ident, _|, $pattern:literal, $($format_vars:expr,)* $(,)?) => {{ use itertools::Itertools; #[allow(unused_variables)] $inputs .into_iter() .enumerate() .map(|($n, $el)| format!($pattern $(, $format_vars)*)) .join(", ") }}; // Default names fallback ($inputs:expr, $pattern:literal) => { splat!($inputs, |n, el|, $pattern) }; } #[derive(Template)] #[template(path = "cpp_header.sptl", escape = "none")] pub(crate) struct CppHeaderTemplate<'a> { pub(crate) panic_to_exception: bool, pub(crate) additional_includes: &'a String, pub(crate) fn_deps: &'a Vec, pub(crate) type_defs: &'a Vec, pub(crate) trait_defs: &'a IndexMap, pub(crate) exported_impls: &'a Vec, pub(crate) exported_fn_defs: &'a Vec, pub(crate) rust_cfg_defines: &'a Vec, pub(crate) zng_header_in_place: bool, pub(crate) namespace: &'a str, pub(crate) crate_name: &'a str, } impl<'a> CppHeaderTemplate<'a> { pub fn cpp_handle_field_name(&self, name: &str) -> String { crate::cpp::cpp_handle_field_name(name) } } #[derive(Template)] #[template(path = "zng_header.sptl", escape = "none")] pub(crate) struct ZngHeaderTemplate { pub(crate) panic_to_exception: bool, pub(crate) cpp_namespace: String, } impl<'a> CppHeaderTemplate<'a> { fn panic_handler(&self) -> String { if self.panic_to_exception { format!( r#" if (__zngur_read_and_reset_rust_panic()) {{ throw ::{}::Panic{{}}; }} "#, self.namespace ) } else { "".to_owned() } } pub fn render_type_methods(&self, td: &crate::cpp::CppTypeDefinition) -> String { use itertools::Itertools; use zngur_def::{Mutability, ZngurMethodReceiver}; let mut s = String::new(); let is_unsized = td.has_unsized(); for method in &td.methods { let ty_str = td.ty.to_string(); let fn_name = format!( "{}::{}", ty_str.strip_prefix("::").unwrap_or(&ty_str), method.name ); let inputs = &method.sig.inputs; let out = &method.sig.output; let splat_inputs = inputs .iter() .enumerate() .map(|(n, ty)| format!("{ty} i{n}")) .join(", "); let splat_skip_inputs = inputs .iter() .skip(1) .enumerate() .map(|(n, ty)| format!("{ty} i{n}")) .join(", "); let mut assume_deinit_str = String::new(); for n in 0..inputs.len() { assume_deinit_str.push_str(&format!( "::{}::__zngur_internal_assume_deinit(i{n}); ", self.namespace )); } let mut rust_args = String::new(); if !inputs.is_empty() { rust_args = inputs .iter() .enumerate() .map(|(n, _)| format!("::{}::__zngur_internal_data_ptr(i{n})", self.namespace)) .join(", ") + ", "; } s.push_str(&format!( r#" inline {out} {fn_name} ({splat_inputs}) noexcept {{ {out} o{{}}; {assume_deinit_str} {rust_link_name} ( {rust_args} ::{namespace}::__zngur_internal_data_ptr(o) ); {panic_handler} ::{namespace}::__zngur_internal_assume_init(o); return o; }} "#, out = out, fn_name = fn_name, splat_inputs = splat_inputs, assume_deinit_str = assume_deinit_str, rust_link_name = method.sig.rust_link_name, rust_args = rust_args, namespace = self.namespace, panic_handler = self.panic_handler() )); if let ZngurMethodReceiver::Ref(m) = method.kind { let ref_kinds: &[&str] = match m { Mutability::Mut => &["RefMut"], Mutability::Not => &["Ref", "RefMut"], }; let field_kinds: &[&str] = match m { Mutability::Mut => &["FieldOwned", "FieldRefMut"], Mutability::Not => &["FieldOwned", "FieldRefMut", "FieldRef"], }; let move_args = (0..(inputs.len().saturating_sub(1))) .map(|n| format!(", ::std::move(i{n})")) .join(""); for field_kind in field_kinds { s.push_str(&format!( r#" template inline {out} {namespace}::{field_kind}< {ty}, Offset, Offsets... >::{method_name}( {splat_skip_inputs} ) const noexcept {{ return {fn_name}( *this {move_args} ); }} "#, out = out, namespace = self.namespace, field_kind = field_kind, ty = td.ty, method_name = method.name, splat_skip_inputs = splat_skip_inputs, fn_name = fn_name, move_args = move_args )); } for ref_kind in ref_kinds { s.push_str(&format!( r#" inline {out} {namespace}::{ref_kind}< {ty} >::{method_name}( {splat_skip_inputs} ) const noexcept {{ return {fn_name}( *this {move_args} ); }} "#, out = out, namespace = self.namespace, ref_kind = ref_kind, ty = td.ty, method_name = method.name, splat_skip_inputs = splat_skip_inputs, fn_name = fn_name, move_args = move_args )); } } if !is_unsized && !td.layout.is_only_by_ref() && method.kind != ZngurMethodReceiver::Static { let this_arg = match method.kind { ZngurMethodReceiver::Ref(_) => "*this", ZngurMethodReceiver::Move => "::std::move(*this)", ZngurMethodReceiver::Static => unreachable!(), }; let const_str = if method.kind == ZngurMethodReceiver::Ref(Mutability::Not) { "const" } else { "" }; let move_args = (0..(inputs.len().saturating_sub(1))) .map(|n| format!(", ::std::move(i{n})")) .join(""); s.push_str(&format!( r#" inline {out} {fn_name}( {splat_skip_inputs} ) {const_str} noexcept {{ return {fn_name}( {this_arg} {move_args} ); }} "#, out = out, fn_name = fn_name, splat_skip_inputs = splat_skip_inputs, const_str = const_str, this_arg = this_arg, move_args = move_args )); } } s } pub fn render_from_trait(&self, td: &crate::cpp::CppTypeDefinition) -> String { use crate::cpp::CppTraitDefinition; use itertools::Itertools; let tr = td.from_trait.as_ref().and_then(|k| self.trait_defs.get(k)); let name = td .ty .to_string() .strip_prefix("::") .unwrap_or(&td.ty.to_string()) .to_string(); match tr { Some(CppTraitDefinition::Fn { sig }) => { let as_std_function = format!( "::std::function< {}({})>", sig.output, sig.inputs.iter().join(", ") ); let ii_names = sig .inputs .iter() .enumerate() .map(|(n, x)| { format!( "::{}::__zngur_internal_move_from_rust< {x} >(i{n})", self.namespace ) }) .join(", "); let uint8_t_ix = if sig.inputs.is_empty() { "".to_owned() } else { sig.inputs .iter() .enumerate() .map(|(n, _ty)| format!("uint8_t* i{n}")) .join(", ") + ", " }; let out_ty = &sig.output; let link_name = &sig.rust_link_name; format!( r#" inline {name} {name}::make_box({as_std_function} f) {{ auto __zngur_data = new {as_std_function}(f); {name} o; ::{namespace}::__zngur_internal_assume_init(o); {link_name} ( reinterpret_cast(__zngur_data), [](uint8_t *d) {{ delete reinterpret_cast< {as_std_function}*>(d); }}, [](uint8_t *d, {uint8_t_ix} uint8_t* o) {{ auto dd = reinterpret_cast< {as_std_function} *>(d); {out_ty} oo = (*dd)({ii_names}); ::{namespace}::__zngur_internal_move_to_rust< {out_ty} >(o, oo); }}, ::{namespace}::__zngur_internal_data_ptr(o) ); return o; }} "#, namespace = self.namespace ) } Some(CppTraitDefinition::Normal { as_ty, link_name, .. }) => { format!( r#" template {name} {name}::make_box(Args&&... args) {{ auto __zngur_data = new T(::std::forward(args)...); auto data_as_impl = dynamic_cast< {as_ty}*>(__zngur_data); {name} o; ::{namespace}::__zngur_internal_assume_init(o); {link_name} ( reinterpret_cast(data_as_impl), [](uint8_t *d) {{ delete reinterpret_cast< {as_ty}*>(d); }}, ::{namespace}::__zngur_internal_data_ptr(o) ); return o; }} "#, namespace = self.namespace ) } None => "".to_owned(), } } pub fn render_from_trait_ref(&self, td: &crate::cpp::CppTypeDefinition) -> String { use crate::cpp::CppTraitDefinition; let tr = td .from_trait_ref .as_ref() .and_then(|k| self.trait_defs.get(k)); let name = td .ty .to_string() .strip_prefix("::") .unwrap_or(&td.ty.to_string()) .to_string(); match tr { Some(CppTraitDefinition::Fn { .. }) => "".to_owned(), Some(CppTraitDefinition::Normal { as_ty, link_name_ref, .. }) => { let mut s = String::new(); for ref_kind in ["Ref", "RefMut"] { s.push_str(&format!( r#" {namespace}::{ref_kind}< {name} >::{ref_kind}({as_ty}& args) {{ auto data_as_impl = &args; ::{namespace}::__zngur_internal_assume_init(*this); {link_name_ref}( (uint8_t *)data_as_impl, ::{namespace}::__zngur_internal_data_ptr(*this) ); }} "#, namespace = self.namespace )); } s } None => "".to_owned(), } } pub fn render_fn_deps(&self) -> String { use itertools::Itertools; let mut s = String::new(); for fd in self.fn_deps { let open_ns = fd.name.open_namespace(); let close_ns = fd.name.close_namespace(); let out = &fd.sig.output; let name = fd.name.name(); let inputs = &fd.sig.inputs; let splat_inputs = inputs .iter() .enumerate() .map(|(n, ty)| format!("{ty} i{n}")) .join(", "); let mut assume_deinit_str = String::new(); for n in 0..inputs.len() { assume_deinit_str.push_str(&format!( "::{}::__zngur_internal_assume_deinit(i{n}); ", self.namespace )); } let mut rust_args = String::new(); if !inputs.is_empty() { rust_args = inputs .iter() .enumerate() .map(|(n, _)| format!("::{}::__zngur_internal_data_ptr(i{n})", self.namespace)) .join(", ") + ", "; } s.push_str(&format!( r#" {open_ns} inline {out} {name}({splat_inputs}) noexcept {{ {out} o{{}}; {assume_deinit_str} {rust_link_name} ( {rust_args} ::{namespace}::__zngur_internal_data_ptr(o) ); {panic_handler} ::{namespace}::__zngur_internal_assume_init(o); return o; }} {close_ns} "#, open_ns = open_ns, out = out, name = name, splat_inputs = splat_inputs, assume_deinit_str = assume_deinit_str, rust_link_name = fd.sig.rust_link_name, rust_args = rust_args, namespace = self.namespace, panic_handler = self.panic_handler(), close_ns = close_ns )); } s } pub fn render_exported_impls(&self) -> String { use itertools::Itertools; let mut s = String::new(); for imp in self.exported_impls { let x = match &imp.tr { Some(x) => format!("{x}"), None => format!("::{}::Inherent", self.namespace), }; s.push_str(&format!( r#" template<> class Impl< {ty}, {x} > {{ public: "#, ty = imp.ty, x = x )); for (name, sig) in &imp.methods { let inputs = &sig.inputs; let splat_inputs = inputs.iter().map(|ty| format!("{ty}")).join(", "); s.push_str(&format!( r#" static {out} {name}( {splat_inputs} ); "#, out = sig.output, name = name, splat_inputs = splat_inputs )); } s.push_str(" };\n"); } s } fn render_zng_header(&self) -> String { let generator = ZngHeaderTemplate { panic_to_exception: self.panic_to_exception, cpp_namespace: self.namespace.to_owned(), }; generator.render().unwrap() } } impl ZngHeaderTemplate { pub fn is_ref_kind_ref(&self, ref_kind: &str) -> bool { ref_kind == "Ref" } pub fn is_size_t(&self, ty: &str) -> bool { ty == "::size_t" } pub fn is_printable(&self, ty: &str) -> bool { ty.starts_with("int") || ty.starts_with("uint") || ty.starts_with("::size_t") || ty.starts_with("::double") || ty.starts_with("::float") } // TODO: Docs - what do these represent? When will we change this list? fn builtin_types(&self) -> Vec { let builtins = [8, 16, 32, 64] .into_iter() .flat_map(|x| [format!("int{x}_t"), format!("uint{x}_t")]) .chain(["::double_t".to_owned(), "::float_t".to_owned()]) .flat_map(|x| { [ x.clone(), format!("::{}::Ref<{x}>", &self.cpp_namespace), format!("::{}::RefMut<{x}>", &self.cpp_namespace), ] }); builtins .chain([ format!("::{}::ZngurCppOpaqueOwnedObject", &self.cpp_namespace), "::size_t".to_owned(), ]) .collect() } } #[derive(Template)] #[template(path = "cpp_source.sptl", escape = "none")] pub(crate) struct CppSourceTemplate<'a> { pub(crate) header_file_name: &'a String, pub(crate) trait_defs: &'a IndexMap, pub(crate) exported_fn_defs: &'a Vec, pub(crate) exported_impls: &'a Vec, pub(crate) cpp_namespace: &'a str, } ================================================ FILE: zngur-generator/templates/cpp_header.sptl ================================================ {# #language:cpp #} #pragma once #include #include #include #include #include #include #include #include #include {% if !self.zng_header_in_place %} #include {% else %} {{ self.render_zng_header() }} {% endif %} {{ self.additional_includes }} {% for def in self.rust_cfg_defines %} #define {{ def }} {% endfor %} extern "C" { {% for f in self.fn_deps %} void {{ f.sig.rust_link_name }} ( {% for n in 0..f.sig.inputs.len() %} uint8_t*, {% endfor %} uint8_t* o ) noexcept ; {% endfor %} {% for td in self.type_defs %} {% for method in td.methods %} void {{ method.sig.rust_link_name }} ( {% for n in 0..method.sig.inputs.len() %} uint8_t*, {% endfor %} uint8_t* o ) noexcept ; {% endfor %} {% for constructor in td.constructors %} void {{ constructor.rust_link_name }} ( {% for n in 0..constructor.inputs.len() %} uint8_t*, {% endfor %} uint8_t* o ) noexcept ; {% endfor %} {% if let Some(cpp_value) = td.cpp_value %} ::{{ self.namespace }}::ZngurCppOpaqueOwnedObject* {{ cpp_value.0 }}(uint8_t*); {% endif %} {% if !td.layout.is_stack_allocated() && !td.layout.is_only_by_ref() %} size_t {{ td.layout.size_fn() }}(); uint8_t* {{ td.layout.alloc_fn() }}(); void {{ td.layout.free_fn() }}(uint8_t*); {% endif %} {% for field in td.fields %} {% if let ZngurFieldDataOffset::Auto(field_offset_const_name) = field.offset %} extern const uint8_t {{ field_offset_const_name }}; {% endif %} {% endfor %} {% for tr in td.wellknown_traits %} {# TODO: switch to match. #} {% if let ZngurWellknownTraitData::Debug { pretty_print, debug_print } = tr %} void {{ pretty_print }}(uint8_t*); void {{ debug_print }}(uint8_t*); {% else if let ZngurWellknownTraitData::Drop { drop_in_place } = tr %} void {{ drop_in_place }}(uint8_t*); {% endif %} {% endfor %} {% for (_, td) in self.trait_defs %} {% if let Some(sig) = td.as_fn() %} void {{ sig.rust_link_name }}(uint8_t* __zngur_data, void destructor(uint8_t *), void call(uint8_t *, {% for n in 0..sig.inputs.len() %} uint8_t *, {% endfor %}uint8_t *o), uint8_t *o ); {% else if let Some(normal) = td.as_normal() %} void {{ normal.2 }}(uint8_t *__zngur_data, void destructor(uint8_t *), uint8_t *o); void {{ normal.3 }}(uint8_t *__zngur_data, uint8_t *o); {% endif %} {% endfor %} // end self.type_defs {% endfor %} } // extern "C" {% for td in self.type_defs %} {{ td.ty.header() }} {% endfor %} {% for imp in self.exported_impls %} {{ imp.ty.header() }} {% if let Some(tr) = imp.tr %} {{ tr.header() }} {% endif %} {% endfor %} namespace {{ self.namespace }} { {% for td in self.type_defs %} {% if td.has_unsized() %} template<> struct zngur_is_unsized< {{ td.ty }} > : ::std::true_type {}; {% endif %} {% endfor %} } {% for (_, td) in self.trait_defs %} {% if let Some(normal) = td.as_normal() %} {{ normal.0.path.open_namespace() }} {{ normal.0.specialization_decl() }} { public: virtual ~{{ normal.0.path.name() }}() {}; {% for method in normal.1 %} virtual {{ method.output }} {{ method.name }} ( {{ splat!(&method.inputs, |n, x|, "{x}") }} ) = 0; {% endfor %} }; {{ normal.0.path.close_namespace() }} {% endif %} {% endfor %} namespace {{ self.namespace }} { // Field specializations templates // (declared before types so fields instantiate the correct template) {% for td in self.type_defs %} {% for field_kind in ["FieldOwned", "FieldRef", "FieldRefMut"] %} template struct {{ field_kind }}< {{ td.ty }}, Offset, Offsets...> { {% if !td.fields.is_empty() %} union { {% for (index, field) in td.fields.iter().enumerate() %} ::{{ self.namespace }}::{{ field_kind }}< {{ field.ty.into_cpp(self.namespace, self.crate_name) }}, {% if let Some(offset) = field.offset.as_offset() %} ::{{ self.namespace }}::FieldStaticOffset< {{ td.ty }}, {{ offset }} >, {% else %} ::{{ self.namespace }}::FieldAutoOffset< {{ td.ty }}, {{ index }} >, {% endif %} Offset, Offsets... > {{ self.cpp_handle_field_name(field.name) }}; {% endfor %} }; {% endif %} {% for method in td.methods %} {% if method.is_valid_field_method(field_kind) %} {{ method.sig.output }} {{ method.name }}( {{ splat!(method.sig.inputs.iter().skip(1), |n, ty|, "{ty}") }} ) const noexcept ; {% endif %} {% endfor %} }; // struct {{ field_kind }}< {{ td.ty }}, Offset, Offsets... > {% endfor %} {% endfor %} } // namespace {{ self.namespace }} {% for td in self.type_defs %} {% let is_copy = td.has_copy() %} {% let is_unsized = td.has_unsized() %} {% let name = td.ty.path.name() %} namespace {{ self.namespace }} { template<> struct __zngur_internal< {{ td.ty }} > { static inline uint8_t* data_ptr(const {{ td.ty }}& t) noexcept ; static inline void check_init(const {{ td.ty }}& t) noexcept ; static inline void assume_init({{ td.ty }}& t) noexcept ; static inline void assume_deinit({{ td.ty }}& t) noexcept ; static inline size_t size_of() noexcept ; }; } {{ td.ty.path.open_namespace() }} {{ td.ty.specialization_decl() }} { public: {% if td.layout.is_only_by_ref() %} {{ name }}() = delete; {% if !td.fields.is_empty() %} union { {% endif %} {% else if td.layout.is_stack_allocated() %} union alignas({{ td.layout.stack_align() }}) { alignas({{ td.layout.stack_align() }}) mutable ::std::array< ::uint8_t, {{ td.layout.stack_size() }}> __zngur_data; {% for (index, field) in td.fields.iter().enumerate() %} ::{{ self.namespace }}::FieldOwned< {{ field.ty.into_cpp(self.namespace, self.crate_name) }}, {% if let Some(offset) = field.offset.as_offset() %} ::{{ self.namespace }}::FieldStaticOffset< {{ td.ty }}, {{ offset }} > {% else %} ::{{ self.namespace }}::FieldAutoOffset< {{ td.ty }}, {{ index }} > {% endif %} > {{ self.cpp_handle_field_name(field.name) }}; {% endfor %} }; {% else %} union { ::uint8_t* __zngur_data; {% for (index, field) in td.fields.iter().enumerate() %} ::{{ self.namespace }}::FieldOwned< {{ field.ty.into_cpp(self.namespace, self.crate_name) }}, {% if let Some(offset) = field.offset.as_offset() %} ::{{ self.namespace }}::FieldStaticOffset< {{ td.ty }}, {{ offset }} > {% else %} ::{{ self.namespace }}::FieldAutoOffset< {{ td.ty }}, {{ index }} > {% endif %} > {{ self.cpp_handle_field_name(field.name) }}; {% endfor %} }; {% endif %} {% if !td.layout.is_only_by_ref() %} {% if td.ty.path.to_string() == format!("::{}::Bool", self.namespace) %} public: operator bool() { return __zngur_data[0]; } Bool(bool b) { __zngur_data[0] = b; } {% endif %} {% if td.ty.path.to_string() == format!("::{}::Char", self.namespace) %} public: operator char32_t() const { return *reinterpret_cast(__zngur_data.data()); } Char(char32_t c) { *reinterpret_cast(__zngur_data.data()) = c; } {% endif %} {% if !is_copy %} bool drop_flag; {% endif %} {% let alloc_heap = td.layout.alloc_heap() %} {% let free_heap = td.layout.free_heap() %} {% let copy_data = td.layout.copy_data() %} public: {% if is_copy %} {{ name }}() { {{ alloc_heap }} } ~{{ name }}() { {{ free_heap }} } {{ name }}(const {{ name }}& other) { {{ alloc_heap }} {{ copy_data }} } {{ name }}& operator=(const {{ name }}& other) { {{ copy_data }} return *this; } {{ name }}({{ name }}&& other) { {{ alloc_heap }} {{ copy_data }} } {{ name }}& operator=({{ name }}&& other) { {{ copy_data }} return *this; } {% else %} {% let drop_in_place = td.drop_in_place() %} {{ name }}() : drop_flag(false) { {{ alloc_heap }} } ~{{ name }}() { if (drop_flag) { {{ drop_in_place }}(::{{ self.namespace }}::__zngur_internal_data_ptr(*this)); } {{ free_heap }} } {{ name }}(const {{ name }}& other) = delete; {{ name }}& operator=(const {{ name }}& other) = delete; {{ name }}({{ name }}&& other) : drop_flag(false) { {{ alloc_heap }} *this = ::std::move(other); } {{ name }}& operator=({{ name }}&& other) { if (this != &other) { if (drop_flag) { {{ drop_in_place }}(::{{ self.namespace }}::__zngur_internal_data_ptr(*this)); } this->drop_flag = other.drop_flag; {{ copy_data }} other.drop_flag = false; } return *this; } {% endif %} {{ td.render_make_box(name, self.namespace, self.crate_name) }} {% if let Some(cpp_value) = td.cpp_value %} inline {{ cpp_value.1 }}& cpp() { return (*{{ cpp_value.0 }}(::{{ self.namespace }}::__zngur_internal_data_ptr(*this))).as_cpp< {{ cpp_value.1 }} >(); } {% endif %} {% endif %} {% for method in td.methods %} static {{ method.sig.output }} {{ method.name }}( {{ splat!(&method.sig.inputs, |n, ty|, "{ty}") }} ) noexcept ; {% if method.kind != ZngurMethodReceiver::Static %} {{ method.sig.output }} {{ method.name }}( {{ splat!(method.sig.inputs.iter().skip(1), |n, ty|, "{ty}") }} ) {% if method.is_ref_not_mut() %} const {% endif %} noexcept ; {% endif %} {% endfor %} {% for constructor in td.constructors %} {{ td.ty.path.0.last().unwrap() }}( {{ splat!(&constructor.inputs, |n, ty|, "{ty}") }} ) noexcept ; {% endfor %} }; // {{ td.ty.specialization_decl() }} {{ td.ty.path.close_namespace() }} namespace {{ self.namespace }} { {% if td.layout.is_stack_allocated() %} inline size_t __zngur_internal< {{ td.ty }} >::size_of() noexcept { return {{ td.layout.stack_size() }}; } {% else if !td.layout.is_only_by_ref() %} inline size_t __zngur_internal< {{ td.ty }} >::size_of() noexcept { return {{ td.layout.size_fn() }}(); } template <> struct zngur_heap_allocated< {{ td.ty }} > : ::std::true_type {}; {% endif %} {% if !td.layout.is_only_by_ref() %} {% if is_copy %} inline void __zngur_internal< {{ td.ty }} >::check_init(const {{ td.ty }}&) noexcept {} inline void __zngur_internal< {{ td.ty }} >::assume_init({{ td.ty }}&) noexcept {} inline void __zngur_internal< {{ td.ty }} >::assume_deinit({{ td.ty }}&) noexcept {} {% else %} inline void __zngur_internal< {{ td.ty }} >::check_init(const {{ td.ty }}& t) noexcept { if (!t.drop_flag) { ::std::cerr << "Use of uninitialized or moved Zngur Rust object with type {{ td.ty }}" << ::std::endl; while (true) raise(SIGSEGV); } } inline void __zngur_internal< {{ td.ty }} >::assume_init({{ td.ty }}& t) noexcept { t.drop_flag = true; } inline void __zngur_internal< {{ td.ty }} >::assume_deinit({{ td.ty }}& t) noexcept { ::{{ self.namespace }}::__zngur_internal_check_init< {{ td.ty }} >(t); t.drop_flag = false; } {% endif %} inline uint8_t* __zngur_internal< {{ td.ty }} >::data_ptr({{ td.ty }} const & t) noexcept { {% if !td.layout.is_stack_allocated() %} return const_cast(t.__zngur_data); {% else %} return const_cast(t.__zngur_data.data()); {% endif %} } {% endif %} {% for (index, field) in td.fields.iter().enumerate() %} {% if let ZngurFieldDataOffset::Auto(field_offset_const_name) = field.offset %} template <> struct FieldAutoOffset< {{ td.ty }}, {{ index }} > { static constexpr size_t offset() noexcept { return {{ field_offset_const_name }}; } }; {% endif %} {% endfor %} } // namespace {{ self.namespace }} namespace {{ self.namespace }} { template<> struct RefMut< {{ td.ty }} > { public: RefMut() { __zngur_data = {% if is_unsized %} {0, 0} {% else %} 0 {% endif %}; } union { {% if is_unsized %}::std::array {% else %}size_t{% endif %} __zngur_data; {% if !is_unsized && !td.layout.is_only_by_ref() %} {% for (index, field) in td.fields.iter().enumerate() %} ::{{ self.namespace }}::FieldRefMut< {{ field.ty.into_cpp(self.namespace, self.crate_name) }}, {% if let Some(offset) = field.offset.as_offset() %} ::{{ self.namespace }}::FieldStaticOffset< {{ td.ty }}, {{ offset }}> {% else %} ::{{ self.namespace }}::FieldAutoOffset< {{ td.ty }}, {{ index }}> {% endif %} > {{ self.cpp_handle_field_name(field.name) }}; {% endfor %} {% endif %} }; {% if !is_unsized && !td.layout.is_only_by_ref() %} RefMut(const {{ td.ty }}& t) { ::{{ self.namespace }}::__zngur_internal_check_init< {{ td.ty }} >(t); __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } {% endif %} {% if !is_unsized %} // construct a ref from a FieldOwned if it's offset can be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldOwned< {{ td.ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldOwned if it's offset can not be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldOwned< {{ td.ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldRefMut if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldRefMut< {{ td.ty }}, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldRefMut< {{ td.ty }}, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } {% endif %} {{ td.render_make_box_ref(td.ty.path.name(), self.namespace, self.crate_name) }} {% if let Some(cpp_value) = td.cpp_value %} inline {{ cpp_value.1 }}& cpp() { return (*{{ cpp_value.0 }}(reinterpret_cast(__zngur_data))).as_cpp< {{ cpp_value.1 }} >(); } {% endif %} {% if let Some(cpp_ref) = td.cpp_ref %} inline {{ cpp_ref.0 }}& cpp() { return *reinterpret_cast< {{ cpp_ref.0 }}* >(__zngur_data); } inline RefMut(const {{ cpp_ref.0 }}& t) : __zngur_data(reinterpret_cast(&t)) {} {% endif %} {% for method in td.methods %} {% if let ZngurMethodReceiver::Ref(_) = method.kind %} {{ method.sig.output }} {{ method.name }}( {{ splat!(method.sig.inputs.iter().skip(1), |n, ty|, "{ty}") }} ) const noexcept ; {% endif %} {% endfor %} }; // struct RefMut< {{ td.ty }} > template<> struct __zngur_internal< RefMut < {{ td.ty }} > > { static inline uint8_t* data_ptr(const RefMut< {{ td.ty }} >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init(RefMut< {{ td.ty }} >&) noexcept {} static inline void check_init(const RefMut< {{ td.ty }} >&) noexcept {} static inline void assume_deinit(RefMut< {{ td.ty }} >&) noexcept {} static inline size_t size_of() noexcept { return {% if is_unsized %}16{% else %}8{% endif %}; } }; } // namespace {{ self.namespace }} // Ref specialization {% if td.ty.path.to_string() == format!("::{}::Str", self.namespace) %} auto operator""_rs(const char* input, size_t len) -> ::{{ self.namespace }}::Ref<::{{ self.namespace }}::Str>; {% endif %} namespace {{ self.namespace }} { template<> struct Ref< {{ td.ty }} > { public: Ref() { __zngur_data = {% if is_unsized %} {0, 0} {% else %} 0 {% endif %}; } union { {% if is_unsized %}::std::array{% else %}size_t{% endif %} __zngur_data; {% if !is_unsized && !td.layout.is_only_by_ref() %} {% for (index, field) in td.fields.iter().enumerate() %} ::{{ self.namespace }}::FieldRef< {{ field.ty.into_cpp(self.namespace, self.crate_name) }}, {% if let Some(offset) = field.offset.as_offset() %} ::{{ self.namespace }}::FieldStaticOffset< {{ td.ty }}, {{ offset }} > {% else %} ::{{ self.namespace }}::FieldAutoOffset< {{ td.ty }}, {{ index }} > {% endif %} > {{ self.cpp_handle_field_name(field.name) }}; {% endfor %} {% endif %} }; {% if !is_unsized && !td.layout.is_only_by_ref() %} Ref(const {{ td.ty }}& t) { ::{{ self.namespace }}::__zngur_internal_check_init< {{ td.ty }} >(t); __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } {% endif %} Ref(RefMut< {{ td.ty }} > rm) { __zngur_data = rm.__zngur_data; } {% if !is_unsized %} // construct a ref from a FieldOwned if it's offset can be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldOwned< {{ td.ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldOwned if it's offset can not be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldOwned< {{ td.ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldRef if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRef< {{ td.ty }}, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRef if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRef< {{ td.ty }}, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRefMut< {{ td.ty }}, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRefMut< {{ td.ty }}, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } {% endif %} {{ td.render_make_box_ref_only(td.ty.path.name(), self.namespace, self.crate_name) }} {% if let Some(cpp_value) = td.cpp_value %} inline {{ cpp_value.1 }}& cpp() { return (*{{ cpp_value.0 }}(reinterpret_cast(__zngur_data))).as_cpp< {{ cpp_value.1 }} >(); } {% endif %} {% if let Some(cpp_ref) = td.cpp_ref %} inline {{ cpp_ref.0 }}& cpp() { return *reinterpret_cast< {{ cpp_ref.0 }}* >(__zngur_data); } inline Ref(const {{ cpp_ref.0 }}& t) : __zngur_data(reinterpret_cast(&t)) {} {% endif %} {% for method in td.methods %} {% if method.is_ref_not_mut() %} {{ method.sig.output }} {{ method.name }}({{ method.render_sig_inputs_skip_one() }}) const noexcept ; {% endif %} {% endfor %} {% if td.ty.path.to_string() == format!("::{}::Str", &self.namespace) %} friend auto ::operator""_rs(const char* input, size_t len) -> ::{{ self.namespace }}::Ref<::{{ self.namespace }}::Str>; {% endif %} }; {% for ref_kind in ["Ref", "Raw", "RawMut"] %} template<> struct __zngur_internal< {{ ref_kind }} < {{ td.ty }} > > { static inline uint8_t* data_ptr(const {{ ref_kind }} < {{ td.ty }} >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init({{ ref_kind }} < {{ td.ty }} >&) noexcept {} static inline void check_init(const {{ ref_kind }} < {{ td.ty }} >&) noexcept {} static inline void assume_deinit({{ ref_kind }} < {{ td.ty }} >&) noexcept {} static inline size_t size_of() noexcept { return {% if is_unsized %}16{% else %}8{% endif %}; } }; {% endfor %} } // namespace {{ self.namespace }} {% if td.ty.path.to_string() == format!("::{}::Str", self.namespace) %} inline ::{{ self.namespace }}::Ref<::{{ self.namespace }}::Str> operator""_rs(const char* input, size_t len) { ::{{ self.namespace }}::Ref<::{{ self.namespace }}::Str> o; o.__zngur_data[0] = reinterpret_cast(input); o.__zngur_data[1] = len; return o; } {% endif %} {% if td.ty.path.to_string() == format!("::{}::Char", self.namespace) %} inline ::{{ self.namespace }}::Char operator""_rs(char32_t c) { if (c > 0x10FFFFu || (c >= 0xD800u && c <= 0xDFFFu)) { return ::{{ self.namespace }}::Char(0xFFFDu); } return ::{{ self.namespace }}::Char(c); } {% endif %} {% endfor %} {% for td in self.type_defs %} {% let cpp_type = td.ty.to_string() %} {% let name = cpp_type.strip_prefix("::").unwrap() %} {% for c in td.constructors %} {% let fn_name = name.to_owned() + "::" + td.ty.path.0.last().unwrap() %} {# do default construction first to ensure allocation #} inline {{ fn_name }}({{ splat!(&c.inputs, |n, ty|, "{ty} i{n}") }}) noexcept : {{ fn_name }}() { ::{{ self.namespace }}::__zngur_internal_assume_init(*this); {{ c.rust_link_name }}( {% for n in 0..c.inputs.len() %} ::{{ self.namespace }}::__zngur_internal_data_ptr(i{{ n }}), {% endfor %} ::{{ self.namespace }}::__zngur_internal_data_ptr(*this) ); {% for n in 0..c.inputs.len() %} ::{{ self.namespace }}::__zngur_internal_assume_deinit(i{{ n }}); {% endfor %} } {% endfor %} {{ self.render_from_trait(td) }} {{ self.render_from_trait_ref(td) }} {{ self.render_type_methods(td) }} namespace {{ self.namespace }} { {% for tr in td.wellknown_traits %} {% if let ZngurWellknownTraitData::Debug { pretty_print, debug_print: _ } = tr %} {% if !td.has_unsized() %} template<> struct ZngurPrettyPrinter< {{ td.ty }} > { static inline void print( {{ td.ty }} const& t) { ::{{ self.namespace }}::__zngur_internal_check_init< {{ td.ty }} >(t); {{ pretty_print }}(::{{ self.namespace }}::__zngur_internal_data_ptr(t)); } }; template<> struct ZngurPrettyPrinter< Ref< {{ td.ty }} > > { static inline void print(Ref< {{ td.ty }} > const& t) { ::{{ self.namespace }}::__zngur_internal_check_init< Ref< {{ td.ty }} > >(t); {{ pretty_print }}(reinterpret_cast(t.__zngur_data)); } }; template<> struct ZngurPrettyPrinter< RefMut< {{ td.ty }} > > { static inline void print(RefMut< {{ td.ty }} > const& t) { ::{{ self.namespace }}::__zngur_internal_check_init< RefMut< {{ td.ty }} > >(t); {{ pretty_print }}(reinterpret_cast(t.__zngur_data)); } }; template struct ZngurPrettyPrinter< FieldOwned< {{ td.ty }}, Offset, Offsets... > > { static inline void print(FieldOwned< {{ td.ty }}, Offset, Offsets... > const& t) { ZngurPrettyPrinter< Ref< {{ td.ty }} > >::print(t); } }; template struct ZngurPrettyPrinter< FieldRef< {{ td.ty }}, Offset, Offsets... > > { static inline void print(FieldRef< {{ td.ty }}, Offset, Offsets... > const& t) { ZngurPrettyPrinter< Ref< {{ td.ty }} > >::print(t); } }; template struct ZngurPrettyPrinter< FieldRefMut< {{ td.ty }}, Offset, Offsets... > > { static inline void print(FieldRefMut< {{ td.ty }}, Offset, Offsets... > const& t) { ZngurPrettyPrinter< Ref< {{ td.ty }} > >::print(t); } }; {% else %} template<> struct ZngurPrettyPrinter< Ref< {{ td.ty }} > > { static inline void print(Ref< {{ td.ty }} > const& t) { ::{{ self.namespace }}::__zngur_internal_check_init< Ref< {{ td.ty }} > >(t); {{ pretty_print }}(::{{ self.namespace }}::__zngur_internal_data_ptr< Ref< {{ td.ty }} > >(t)); } }; template<> struct ZngurPrettyPrinter< RefMut< {{ td.ty }} > > { static inline void print(RefMut< {{ td.ty }} > const& t) { ::{{ self.namespace }}::__zngur_internal_check_init< RefMut< {{ td.ty }} > >(t); {{ pretty_print }}(::{{ self.namespace }}::__zngur_internal_data_ptr< RefMut< {{ td.ty }} > >(t)); } }; {% endif %} {% endif %} {% endfor %} } // namespace {{ self.namespace }} {% endfor %} {{ self.render_fn_deps() }} namespace {{ self.namespace }} { namespace exported_functions { {% for func in self.exported_fn_defs %} {{ func.sig.output }} {{ func.name }}( {{ splat!(&func.sig.inputs, |n, ty|, "{ty}") }} ); {% endfor %} } // namespace exported_functions {{ self.render_exported_impls() }} } // namespace {{ self.namespace }} ================================================ FILE: zngur-generator/templates/cpp_source.sptl ================================================ #include "{{ self.header_file_name }}" extern "C" { {% for (_, td) in self.trait_defs %} {% if let Some(normal) = td.as_normal() %} {% for method in normal.1 %} void {{ method.rust_link_name }}( uint8_t* data {% for n in 0..method.inputs.len() %}, uint8_t* i{{ n }}{% endfor %}, uint8_t* o ) { {{ normal.0 }}* data_typed = reinterpret_cast< {{ normal.0 }}* >(data); {{ method.output }} oo = data_typed->{{ method.name }}( {{ method.render_inputs(self.cpp_namespace) }} ); ::{{ self.cpp_namespace }}::__zngur_internal_move_to_rust(o, oo); } {% endfor %} {% endif %} {% endfor %} {% for func in self.exported_fn_defs %} void {{ func.sig.rust_link_name }}( {% for n in 0..func.sig.inputs.len() %}uint8_t* i{{ n }}, {% endfor %}uint8_t* o ) { {{ func.sig.output }} oo = ::{{ self.cpp_namespace }}::exported_functions::{{ func.name }}( {{ func.sig.render_inputs(self.cpp_namespace) }} ); ::{{ self.cpp_namespace }}::__zngur_internal_move_to_rust(o, oo); } {% endfor %} {% for imp in self.exported_impls %} {% for (name, sig) in imp.methods %} void {{ sig.rust_link_name }}( {% for n in 0..sig.inputs.len() %}uint8_t* i{{ n }}, {% endfor %}uint8_t* o ) { {{ sig.output }} oo = ::{{ self.cpp_namespace }}::Impl< {{ imp.ty }}, {{ imp.render_tr(self.cpp_namespace) }} >::{{ name }}( {{ sig.render_inputs(self.cpp_namespace) }} ); ::{{ self.cpp_namespace }}::__zngur_internal_move_to_rust(o, oo); } {% endfor %} {% endfor %} } // extern "C" ================================================ FILE: zngur-generator/templates/zng_header.sptl ================================================ {# #language:cpp #} #ifndef __ZNG_HEADER_INTERNAL_TYPES #define __ZNG_HEADER_INTERNAL_TYPES #include #include #include #include #include #include #include #include #include {% if self.panic_to_exception %} namespace {{ self.cpp_namespace }} { class Panic {}; } inline thread_local bool __zngur_rust_panicked_flag(false); #ifdef _MSC_VER // MSVC: Functions use standard inline. Variables use selectany to prevent // multiple-definition errors when included in multiple .cpp files. #define ZNGUR_FFI_EXPORT_FUNC extern "C" inline #define ZNGUR_FFI_EXPORT_VAR extern "C" __declspec(selectany) #elif defined(__GNUC__) || defined(__clang__) // GCC/Clang: 'used' prevents the linker from stripping them. 'weak' on // the variable tells the linker to merge multiple definitions into one. #define ZNGUR_FFI_EXPORT_FUNC extern "C" inline __attribute__((used)) #define ZNGUR_FFI_EXPORT_VAR extern "C" __attribute__((weak, used)) #else #define ZNGUR_FFI_EXPORT_FUNC extern "C" inline #define ZNGUR_FFI_EXPORT_VAR extern "C" #endif ZNGUR_FFI_EXPORT_FUNC void __zngur_mark_panicked() { // TODO: While thread safe, you may end up with the wrong thread being // notified of a panic __zngur_rust_panicked_flag = true; } ZNGUR_FFI_EXPORT_FUNC bool __zngur_read_and_reset_rust_panic() { bool out = __zngur_rust_panicked_flag; __zngur_rust_panicked_flag = false; return out; } // Force compilers that don't have `__attribute__((used))` to still export the functions by keeping // function pointers. ZNGUR_FFI_EXPORT_VAR void (* const __keep_zngur_mark_panicked)() = &__zngur_mark_panicked; ZNGUR_FFI_EXPORT_VAR bool (* const __keep_zngur_read_and_reset_panic)() = &__zngur_read_and_reset_rust_panic; {% endif %} #ifndef zngur_dbg #define zngur_dbg(x) (::{{ self.cpp_namespace }}::zngur_dbg_impl(__FILE__, __LINE__, #x, x)) #endif // zngur_dbg namespace {{ self.cpp_namespace }} { inline void* __zng_memcpy( void* dest, const void* src, std::size_t count ){ if (count > 0) { return memcpy(dest, src, count); } return dest; } template struct __zngur_internal { static inline uint8_t* data_ptr(const T& t) noexcept; static void assume_init(T& t) noexcept ; static void assume_deinit(T& t) noexcept ; static inline void check_init(const T&) noexcept; static inline size_t size_of() noexcept ; }; template inline uint8_t* __zngur_internal_data_ptr(const T& t) noexcept { return __zngur_internal::data_ptr(t); } template void __zngur_internal_assume_init(T& t) noexcept { __zngur_internal::assume_init(t); } template void __zngur_internal_assume_deinit(T& t) noexcept { __zngur_internal::assume_deinit(t); } template inline size_t __zngur_internal_size_of() noexcept { return __zngur_internal::size_of(); } template inline void __zngur_internal_move_to_rust(uint8_t* dst, T& t) noexcept { __zng_memcpy(dst, ::{{ self.cpp_namespace }}::__zngur_internal_data_ptr(t), ::{{ self.cpp_namespace }}::__zngur_internal_size_of()); ::{{ self.cpp_namespace }}::__zngur_internal_assume_deinit(t); } template inline T __zngur_internal_move_from_rust(uint8_t* src) noexcept { T t; ::{{ self.cpp_namespace }}::__zngur_internal_assume_init(t); __zng_memcpy(::{{ self.cpp_namespace }}::__zngur_internal_data_ptr(t), src, ::{{ self.cpp_namespace }}::__zngur_internal_size_of()); return t; } template inline void __zngur_internal_check_init(const T& t) noexcept { __zngur_internal::check_init(t); } class ZngurCppOpaqueOwnedObject { uint8_t* __zngur_data; void (*destructor)(uint8_t*); public: template inline static ZngurCppOpaqueOwnedObject build(Args&&... args) { ZngurCppOpaqueOwnedObject o; o.__zngur_data = reinterpret_cast(new T(::std::forward(args)...)); o.destructor = [](uint8_t* d) { delete reinterpret_cast(d); }; return o; } template inline T& as_cpp() { return *reinterpret_cast(__zngur_data); } }; template struct Ref; template struct RefMut; // offset encoded in type template struct FieldStaticOffset {}; // specialized per type to acquire offset from `extern const size_t` template struct FieldAutoOffset { static size_t offset() noexcept; }; // specialize to record allocation strategy into type system template struct zngur_heap_allocated : ::std::false_type {}; namespace zngur_detail { // std::conjunction analog for c++11 support template struct conjunction : std::true_type {}; template struct conjunction : std::conditional, std::false_type>::type {}; // Offset can be computed at compile time template struct is_static_offset : std::false_type {}; // a static offset not behind a pointer can be computed template struct is_static_offset> : std::true_type {}; // All offsets can be compile time computed template using all_static_offset = conjunction, is_static_offset...>; } // namespace zngur_detail template struct __zngur_internal_field { // offset for this type and offset static constexpr size_t offset(); // is this offset behind a pointer static constexpr bool heap_allocated(); }; template struct __zngur_internal_field> { static constexpr size_t offset() { return OFFSET; } static constexpr bool heap_allocated() { return zngur_heap_allocated::value; } }; template struct __zngur_internal_field> { static size_t offset() { return FieldAutoOffset::offset(); } static constexpr bool heap_allocated() { return zngur_heap_allocated::value; } }; // NOTE: Offsets are in reverse order, last offset first. // this allows offsets to be prepended in versions of c++ that don't have fold // expressions (pre c++17) template struct __zngur_internal_calc_field { // used to statically calculate a total offset static constexpr size_t offset(); // used to check if the owner of the first offset is heap allocated static constexpr bool heap_allocated(); }; // base case template struct __zngur_internal_calc_field { static constexpr size_t offset() { return __zngur_internal_field::offset(); } static constexpr bool heap_allocated() { return __zngur_internal_field::heap_allocated(); } }; // recursively calculate offsets for Fields template struct __zngur_internal_calc_field { static constexpr size_t offset() { return __zngur_internal_calc_field::offset() + __zngur_internal_field::offset(); } static constexpr bool heap_allocated() { return __zngur_internal_calc_field::heap_allocated(); } }; template struct FieldOwned { inline operator T() const noexcept { return *::{{ self.cpp_namespace }}::Ref(*this); } }; template struct FieldRef { inline operator T() const noexcept { return *::{{ self.cpp_namespace }}::Ref(*this); } }; template struct FieldRefMut { inline operator T() const noexcept { return *::{{ self.cpp_namespace }}::Ref(*this); } }; #if __cplusplus >= 201703L // deduction guides template Ref(const FieldOwned &f) -> Ref; template Ref(const FieldRef &f) -> Ref; template Ref(const FieldRefMut &f) -> Ref; template RefMut(const FieldOwned &f) -> RefMut; template RefMut(const FieldRef &f) -> RefMut; template RefMut(const FieldRefMut &f) -> RefMut; #endif template struct zngur_is_unsized : ::std::false_type {}; struct zngur_fat_pointer { uint8_t* __zngur_data; size_t metadata; }; template struct Raw { using DataType = typename ::std::conditional< zngur_is_unsized::value, zngur_fat_pointer, uint8_t* >::type; DataType __zngur_data; Raw() {} Raw(Ref value) { __zng_memcpy(&__zngur_data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); } Raw(RefMut value) { __zng_memcpy(&__zngur_data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); } Raw(DataType data) : __zngur_data(data) { } Raw offset(ptrdiff_t n) { return Raw(__zngur_data + n * __zngur_internal_size_of()); } Ref read_ref() { Ref value; __zng_memcpy(__zngur_internal_data_ptr>(value), &__zngur_data, __zngur_internal_size_of>()); __zngur_internal_assume_init>(value); return value; } }; template struct RawMut { using DataType = typename ::std::conditional< zngur_is_unsized::value, zngur_fat_pointer, uint8_t* >::type; DataType __zngur_data; RawMut() {} RawMut(RefMut value) { __zng_memcpy(&__zngur_data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); } RawMut(DataType data) : __zngur_data(data) { } RawMut offset(ptrdiff_t n) { return RawMut(__zngur_data + n * __zngur_internal_size_of()); } T read() { T value; __zng_memcpy(__zngur_internal_data_ptr(value), __zngur_data, __zngur_internal_size_of()); __zngur_internal_assume_init(value); return value; } Ref read_ref() { Ref value; __zng_memcpy(__zngur_internal_data_ptr>(value), &__zngur_data, __zngur_internal_size_of>()); __zngur_internal_assume_init>(value); return value; } RefMut read_mut() { RefMut value; __zng_memcpy(__zngur_internal_data_ptr>(value), &__zngur_data, __zngur_internal_size_of>()); __zngur_internal_assume_init>(value); return value; } void write(T value) { __zng_memcpy(__zngur_data, __zngur_internal_data_ptr(value), __zngur_internal_size_of()); __zngur_internal_assume_deinit(value); } }; template struct Tuple; template<> struct Tuple< > { public: union alignas(1) { alignas(1) mutable ::std::array< ::uint8_t, 0> __zngur_data; }; }; using Unit = Tuple<>; template<> struct __zngur_internal< ::{{ self.cpp_namespace }}::Unit > { static inline uint8_t* data_ptr(const ::{{ self.cpp_namespace }}::Unit& t) noexcept { return const_cast(t.__zngur_data.data()); } static inline void check_init(const ::{{ self.cpp_namespace }}::Unit&) noexcept {} static inline void assume_init(::{{ self.cpp_namespace }}::Unit&) noexcept {} static inline void assume_deinit(::{{ self.cpp_namespace }}::Unit&) noexcept {} static inline size_t size_of() noexcept { return 0; } }; template<> struct RefMut< ::{{ self.cpp_namespace }}::Unit > { public: RefMut() { __zngur_data = 0; } union { size_t __zngur_data; }; RefMut(const ::{{ self.cpp_namespace }}::Unit& t) { __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } template ::value, bool>::type = true> RefMut(const FieldOwned< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } template ::value, bool>::type = true> RefMut(const FieldOwned< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } template ::value, bool>::type = true> RefMut(const FieldRefMut< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } template ::value, bool>::type = true> RefMut(const FieldRefMut< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } }; template<> struct __zngur_internal< RefMut < ::{{ self.cpp_namespace }}::Unit > > { static inline uint8_t* data_ptr(const RefMut< ::{{ self.cpp_namespace }}::Unit >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init(RefMut< ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void check_init(const RefMut< ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void assume_deinit(RefMut< ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline size_t size_of() noexcept { return 8; } }; template<> struct Ref< ::{{ self.cpp_namespace }}::Unit > { public: Ref() { __zngur_data = 0; } union { size_t __zngur_data; }; Ref(const ::{{ self.cpp_namespace }}::Unit& t) { __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } Ref(RefMut< ::{{ self.cpp_namespace }}::Unit > rm) { __zngur_data = rm.__zngur_data; } template ::value, bool>::type = true> Ref(const FieldOwned< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } template ::value, bool>::type = true> Ref(const FieldOwned< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } template ::value, bool>::type = true> Ref(const FieldRef< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } template ::value, bool>::type = true> Ref(const FieldRef< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } template ::value, bool>::type = true> Ref(const FieldRefMut< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } template ::value, bool>::type = true> Ref(const FieldRefMut< ::{{ self.cpp_namespace }}::Unit, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } }; template<> struct __zngur_internal< Ref < ::{{ self.cpp_namespace }}::Unit > > { static inline uint8_t* data_ptr(const Ref < ::{{ self.cpp_namespace }}::Unit >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init(Ref < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void check_init(const Ref < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void assume_deinit(Ref < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline size_t size_of() noexcept { return 8; } }; template<> struct __zngur_internal< Raw < ::{{ self.cpp_namespace }}::Unit > > { static inline uint8_t* data_ptr(const Raw < ::{{ self.cpp_namespace }}::Unit >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init(Raw < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void check_init(const Raw < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void assume_deinit(Raw < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline size_t size_of() noexcept { return 8; } }; template<> struct __zngur_internal< RawMut < ::{{ self.cpp_namespace }}::Unit > > { static inline uint8_t* data_ptr(const RawMut < ::{{ self.cpp_namespace }}::Unit >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init(RawMut < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void check_init(const RawMut < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline void assume_deinit(RawMut < ::{{ self.cpp_namespace }}::Unit >&) noexcept {} static inline size_t size_of() noexcept { return 8; } }; template struct ZngurPrettyPrinter; class Inherent; template class Impl; template T&& zngur_dbg_impl(const char* file_name, int line_number, const char* exp, T&& input) { ::std::cerr << "[" << file_name << ":" << line_number << "] " << exp << " = "; ZngurPrettyPrinter::type>::print(input); return ::std::forward(input); } // specializations for Refs of Refs {% for ref_kind in ["Ref", "RefMut"] %} {% for nested_ref_kind in ["Ref", "RefMut"] %} template struct {{ ref_kind }} < {{ nested_ref_kind }} < T > > { {{ ref_kind }}() { __zngur_data = 0; } {{ ref_kind }}(const {{ nested_ref_kind }} < T >& t) { __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } // construct a ref from a FieldOwned if it's offset can be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> {{ ref_kind }}(const FieldOwned< {{ nested_ref_kind }} < T >, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldOwned if it's offset can not be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> {{ ref_kind }}(const FieldOwned< {{ nested_ref_kind }} < T >, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } {% if self.is_ref_kind_ref(ref_kind) %} // construct a ref from a FieldRef if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> {{ ref_kind }}(const FieldRef< {{ nested_ref_kind }} < T >, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRef if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> {{ ref_kind }}(const FieldRef< {{ nested_ref_kind }} < T >, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } {% endif %} // construct a ref from a FieldRefMut if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> {{ ref_kind }}(const FieldRefMut< {{ nested_ref_kind }} < T >, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> {{ ref_kind }}(const FieldRefMut< {{ nested_ref_kind }} < T >, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } {{ nested_ref_kind }}< T >& operator*() { return *reinterpret_cast< {{ nested_ref_kind }} < T >*>(__zngur_data); } {{ nested_ref_kind }}< T > const& operator*() const { return *reinterpret_cast< {{ nested_ref_kind }} < T >*>(__zngur_data); } private: size_t __zngur_data; friend ::{{ self.cpp_namespace }}::__zngur_internal< {{ ref_kind }} < {{ nested_ref_kind }} < T > > >; friend ::{{ self.cpp_namespace }}::ZngurPrettyPrinter< {{ ref_kind }} < {{ nested_ref_kind }} < T > > >; }; template struct __zngur_internal< {{ ref_kind }} < {{ nested_ref_kind }} < T > > > { static inline uint8_t* data_ptr(const {{ ref_kind }} < {{ nested_ref_kind }} < T > >& t) noexcept { return const_cast(reinterpret_cast(&t.__zngur_data)); } static inline void assume_init({{ ref_kind }} < {{ nested_ref_kind }} < T > >&) noexcept {} static inline void check_init(const {{ ref_kind }} < {{ nested_ref_kind }} < T > >&) noexcept {} static inline void assume_deinit({{ ref_kind }} < {{ nested_ref_kind }} < T > >&) noexcept {} static inline size_t size_of() noexcept { return __zngur_internal_size_of< {{ nested_ref_kind }} < T > >({}); } }; template struct ZngurPrettyPrinter< {{ ref_kind }} < {{ nested_ref_kind }} < T > > > { static inline void print({{ ref_kind }} < {{ nested_ref_kind }} < T > > const& t) { ::{{ self.cpp_namespace }}::__zngur_internal_check_init(t); ::{{ self.cpp_namespace }}::ZngurPrettyPrinter< {{ nested_ref_kind }} < T > >::print( reinterpret_cast< const {{ nested_ref_kind }} < T > &>(t.__zngur_data) ); } }; {% endfor %} {% endfor %} {% for ty in self.builtin_types() %} {% let needs_endif = self.is_size_t(ty) %} {% if needs_endif %} #if defined(__APPLE__) || defined(__wasm__) {% endif %} template<> struct __zngur_internal< {{ ty }} > { static inline uint8_t* data_ptr(const {{ ty }}& t) noexcept { return const_cast(reinterpret_cast(&t)); } static inline void assume_init({{ ty }}&) noexcept {} static inline void assume_deinit({{ ty }}&) noexcept {} static inline void check_init({{ ty }}&) noexcept {} static inline size_t size_of() noexcept { return sizeof({{ ty }}); } }; template<> struct __zngur_internal< {{ ty }}* > { static inline uint8_t* data_ptr({{ ty }}* const & t) noexcept { return const_cast(reinterpret_cast(&t)); } static inline void assume_init({{ ty }}*&) noexcept {} static inline void assume_deinit({{ ty }}*&) noexcept {} static inline void check_init({{ ty }}*&) noexcept {} static inline size_t size_of() noexcept { return sizeof({{ ty }}); } }; template<> struct __zngur_internal< {{ ty }} const* > { static inline uint8_t* data_ptr({{ ty }} const* const & t) noexcept { return const_cast(reinterpret_cast(&t)); } static inline void assume_init({{ ty }} const*&) noexcept {} static inline void assume_deinit({{ ty }} const*&) noexcept {} static inline void check_init({{ ty }} const*&) noexcept {} static inline size_t size_of() noexcept { return sizeof({{ ty }}); } }; template<> struct Ref< {{ ty }} > { Ref() { __zngur_data = 0; } Ref(const {{ ty }}& t) { __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } // construct a ref from a FieldOwned if all of it's offsets can be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldOwned< {{ ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldOwned if any of it's offsets can not be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldOwned< {{ ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldRef if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRef< {{ ty }}, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRef if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRef< {{ ty }}, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRefMut< {{ ty }}, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> Ref(const FieldRefMut< {{ ty }}, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } {{ ty }}& operator*() { return *reinterpret_cast< {{ ty }}*>(__zngur_data); } {{ ty }} const& operator*() const { return *reinterpret_cast< {{ ty }}*>(__zngur_data); } private: size_t __zngur_data; friend ::{{ self.cpp_namespace }}::__zngur_internal >; friend ::{{ self.cpp_namespace }}::ZngurPrettyPrinter< Ref< {{ ty }} > >; }; template<> struct RefMut< {{ ty }} > { RefMut() { __zngur_data = 0; } RefMut({{ ty }}& t) { __zngur_data = reinterpret_cast(__zngur_internal_data_ptr(t)); } // construct a ref from a FieldOwned if all of it's offsets can be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldOwned< {{ ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); constexpr size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldOwned if any of it's offsets can not be calculated at // compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldOwned< {{ ty }}, Offset, Offsets... >& f) { constexpr bool heap_allocated = __zngur_internal_calc_field::heap_allocated(); size_t offset = __zngur_internal_calc_field::offset(); if (heap_allocated) { __zngur_data = *reinterpret_cast(&f) + offset; } else { __zngur_data = reinterpret_cast(&f) + offset; } } // construct a ref from a FieldRefMut if all of it's offsets can be calculated // at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldRefMut< {{ ty }}, Offset, Offsets...>& f) { constexpr size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } // construct a ref from a FieldRefMut if any of it's offsets can not be // calculated at compile time template < typename Offset, typename... Offsets, typename ::std::enable_if< !zngur_detail::all_static_offset::value, bool>::type = true> RefMut(const FieldRefMut< {{ ty }}, Offset, Offsets...>& f) { size_t offset = __zngur_internal_calc_field::offset(); __zngur_data = *reinterpret_cast(&f) + offset; } {{ ty }}& operator*() { return *reinterpret_cast< {{ ty }}*>(__zngur_data); } {{ ty }} const& operator*() const { return *reinterpret_cast< {{ ty }}*>(__zngur_data); } private: size_t __zngur_data; friend ::{{ self.cpp_namespace }}::__zngur_internal >; friend ::{{ self.cpp_namespace }}::ZngurPrettyPrinter< Ref< {{ ty }} > >; }; {% let printable = self.is_printable(ty) %} {% if printable %} template<> struct ZngurPrettyPrinter< {{ ty }} > { static inline void print({{ ty }} const& t) { ::std::cerr << t << ::std::endl; } }; template<> struct ZngurPrettyPrinter< Ref< {{ ty }} > > { static inline void print(Ref< {{ ty }} > const& t) { ::std::cerr << *t << ::std::endl; } }; template<> struct ZngurPrettyPrinter< RefMut< {{ ty }} > > { static inline void print(RefMut< {{ ty }} > const& t) { ::std::cerr << *t << ::std::endl; } }; {% endif %} {% if needs_endif %} #endif {% endif %} // end builtin types {% endfor %} } // namespace {{ self.cpp_namespace }} #endif // __ZNG_HEADER_INTERNAL_TYPES ================================================ FILE: zngur-parser/Cargo.toml ================================================ [package] name = "zngur-parser" description = "Parser of the zng file" readme = "../README.md" version = "0.9.0" edition.workspace = true rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ariadne = "0.3.0" chumsky = { version = "=1.0.0-alpha.8", features = [] } itertools = "0.11" zngur-def = { version = "=0.9.0", path = "../zngur-def" } [dev-dependencies] expect-test = "1.4.1" strip-ansi-escapes = "0.2.0" ================================================ FILE: zngur-parser/src/cfg.rs ================================================ use std::collections::{HashMap, HashSet}; use crate::{ ParseContext, Span, Spanned, Token, ZngParser, conditional::{MatchPattern, MatchPatternParse, Matchable, MatchableParse}, spanned, }; use chumsky::prelude::*; /// A configuration provider, Must be Clone. pub trait RustCfgProvider: CloneableCfg { /// Gets values associated with a config key if it's present. fn get_cfg(&self, key: &str) -> Option>; /// Gets a list of feature names that are enabled fn get_features(&self) -> Vec; /// Gets all config key:value pairs /// /// Returns a pair for every value of a key, if a key has no values emits a /// (key, None) pair fn get_cfg_pairs(&self) -> Vec<(String, Option)>; } pub trait CloneableCfg { fn clone_box(&self) -> Box; } impl CloneableCfg for T where T: 'static + RustCfgProvider + Clone, { fn clone_box(&self) -> Box { Box::new(self.clone()) } } #[derive(Copy, Clone)] pub struct NullCfg; impl RustCfgProvider for NullCfg { fn get_cfg(&self, _key: &str) -> Option> { None } fn get_features(&self) -> Vec { Vec::new() } fn get_cfg_pairs(&self) -> Vec<(String, Option)> { Vec::new() } } #[derive(Clone)] pub struct InMemoryRustCfgProvider { cfg: HashMap>, } const CARGO_FEATURE_PREFIX: &str = "CARGO_FEATURE_"; const CARGO_CFG_PREFIX: &str = "CARGO_CFG_"; impl InMemoryRustCfgProvider { pub fn new() -> Self { InMemoryRustCfgProvider { cfg: HashMap::new(), } } pub fn with_values<'a, CfgPairs, CfgKey, CfgValues>(mut self, cfg_values: CfgPairs) -> Self where CfgPairs: IntoIterator, CfgKey: AsRef + 'a, CfgValues: Clone + IntoIterator + 'a, ::Item: AsRef, { for (key, values) in cfg_values { let entry = self.cfg.entry(key.as_ref().to_string()).or_default(); let values = values.clone().into_iter().map(|v| v.as_ref().to_string()); entry.reserve(values.size_hint().0); for value in values { if !entry.contains(&value) { entry.push(value); } } } self } pub fn load_from_cargo_env(mut self) -> Self { // set to unify features that can appear in two locations let mut features = HashSet::new(); for (k, v) in std::env::vars_os() { // no panic if not unicode let (Some(k), Some(v)) = (k.to_str(), v.to_str()) else { continue; }; if let Some(feature) = k.strip_prefix(CARGO_FEATURE_PREFIX) { features.insert(feature.to_lowercase()); } else if let Some(key) = k.strip_prefix(CARGO_CFG_PREFIX) { let key = key.to_lowercase(); let values: Vec = v.split(",").map(str::to_owned).collect(); if key == "feature" { features.extend(values); } else { let entry = self.cfg.entry(key.to_string()).or_default(); entry.reserve(values.len()); for value in values { if !entry.contains(&value) { entry.push(value); } } } } } if !features.is_empty() { let features_entry = self.cfg.entry("feature".to_string()).or_default(); features_entry.reserve(features.len()); features_entry.extend(features); } self } } impl Default for InMemoryRustCfgProvider { fn default() -> Self { Self::new() } } impl RustCfgProvider for InMemoryRustCfgProvider { fn get_cfg(&self, key: &str) -> Option> { self.cfg.get(key).map(|values| values.to_vec()) } fn get_features(&self) -> Vec { self.cfg.get("feature").cloned().unwrap_or_default() } fn get_cfg_pairs(&self) -> Vec<(String, Option)> { self.cfg .iter() .flat_map(|(key, values)| { if values.is_empty() { vec![(key.clone(), None)] } else { values .iter() .map(|value| (key.clone(), Some(value.clone()))) .collect() } }) .collect() } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum CfgScrutinee<'src> { Key(&'src str), KeyWithItem(&'src str, &'src str), Feature(&'src str), AllFeatures, } #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ProcessedCfgScrutinee { Empty, Some, Values(Vec), } #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ProcessedCfgConditional { Single(ProcessedCfgScrutinee), Tuple(Vec), } /// Match on config keys and features #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum CfgConditional<'src> { Single(CfgScrutinee<'src>), Tuple(Vec>), } #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum CfgPatternItem<'src> { Empty, // a `_` pattern Some, // the config has "some" value for the key None, // the config has "no" value for the key Str(&'src str), Number(usize), } #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum CfgPattern<'src> { Single(CfgPatternItem<'src>, Span), And(Vec>, Span), Or(Vec>, Span), Not(Box>, Span), Grouped(Box>, Span), Tuple(Vec>, Span), } impl<'src> MatchPattern for CfgPattern<'src> { fn default_some(span: Span) -> Self { CfgPattern::Single(CfgPatternItem::Some, span) } } impl<'src> MatchPatternParse<'src> for CfgPattern<'src> { fn parser() -> impl ZngParser<'src, Self> { let single = recursive(|pat| { let literals = select! { Token::Str(c) => CfgPatternItem::Str(c), Token::Number(n) => CfgPatternItem::Number(n), }; let atom = choice(( spanned(literals), spanned(just(Token::Underscore).to(CfgPatternItem::Empty)), spanned(just(Token::Ident("Some")).to(CfgPatternItem::Some)), spanned(just(Token::Ident("None")).to(CfgPatternItem::None)), )) .map(|item| CfgPattern::Single(item.inner, item.span)) .or(spanned( pat.delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map(|item| CfgPattern::Grouped(Box::new(item.inner), item.span))); let not_pat = just(Token::Bang) .repeated() .foldr_with(atom, |_op, rhs, e| CfgPattern::Not(Box::new(rhs), e.span())); let and_pat = not_pat.clone().foldl_with( just(Token::And).ignore_then(not_pat).repeated(), |lhs, rhs, e| match lhs { CfgPattern::And(mut items, _span) => { items.push(rhs); CfgPattern::And(items, e.span()) } _ => CfgPattern::And(vec![lhs, rhs], e.span()), }, ); // or pat and_pat.clone().foldl_with( just(Token::Pipe).ignore_then(and_pat).repeated(), |lhs, rhs, e| match lhs { CfgPattern::Or(mut items, _span) => { items.push(rhs); CfgPattern::Or(items, e.span()) } _ => CfgPattern::Or(vec![lhs, rhs], e.span()), }, ) }); spanned( single .clone() .separated_by(just(Token::Comma)) .at_least(1) .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map(|item| CfgPattern::Tuple(item.inner, item.span)) .or(single) } } impl<'src> Matchable for CfgConditional<'src> { type Pattern = CfgPattern<'src>; fn eval(&self, pattern: &Self::Pattern, ctx: &mut ParseContext) -> bool { let cfg = ctx.get_config_provider(); let process = |key: &CfgScrutinee<'src>| -> ProcessedCfgScrutinee { match key { CfgScrutinee::Key(key) => cfg .get_cfg(key) .map(|values| { if values.is_empty() { ProcessedCfgScrutinee::Some } else { ProcessedCfgScrutinee::Values(values) } }) .unwrap_or(ProcessedCfgScrutinee::Empty), CfgScrutinee::KeyWithItem(key, item) => cfg .get_cfg(key) .and_then(|values| { values .iter() .any(|value| value == item) .then_some(ProcessedCfgScrutinee::Some) }) .unwrap_or(ProcessedCfgScrutinee::Empty), CfgScrutinee::AllFeatures => ProcessedCfgScrutinee::Values(cfg.get_features()), CfgScrutinee::Feature(feature) => { if cfg.get_features().iter().any(|value| value == feature) { ProcessedCfgScrutinee::Some } else { ProcessedCfgScrutinee::Empty } } } }; let scrutinee = match self { Self::Single(key) => ProcessedCfgConditional::Single(process(key)), Self::Tuple(keys) => { ProcessedCfgConditional::Tuple(keys.iter().map(process).collect::>()) } }; pattern.matches(&scrutinee, ctx) } } impl<'src> CfgScrutinee<'src> { fn parser() -> impl ZngParser<'src, Self> { select! {Token::Ident(c) => c, Token::Str(s) => s} .separated_by(just(Token::Dot)) .at_least(1) .at_most(2) .collect::>() .map(|item| { match &item[..] { [key] if key == &"feature" => CfgScrutinee::AllFeatures, [key, item] if key == &"feature" => CfgScrutinee::Feature(item), [key] => CfgScrutinee::Key(key), [key, item] => CfgScrutinee::KeyWithItem(key, item), // the above at_least(1) and at_most(2) calls // prevent this branch _ => unreachable!(), } }) } } impl<'src> MatchableParse<'src> for CfgConditional<'src> { fn parser() -> impl ZngParser<'src, Self> { let directive = just([Token::Ident("cfg"), Token::Bang]).ignore_then( CfgScrutinee::parser().delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ); choice(( directive.clone().map(CfgConditional::Single), directive .separated_by(just(Token::Comma)) .allow_trailing() .at_least(1) .collect::>() .map(CfgConditional::Tuple) .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), )) } fn combined() -> Option< crate::BoxedZngParser< 'src, ( crate::Spanned, crate::Spanned<::Pattern>, ), >, > { let directive = just([Token::Ident("cfg"), Token::Bang]) .ignore_then( spanned(CfgScrutinee::parser()) .then( just(Token::Eq) .ignore_then(spanned(CfgPattern::parser())) .or_not(), ) .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map_with(|(scrutinee, pat), e| { ( scrutinee, pat.unwrap_or_else(|| Spanned { inner: CfgPattern::default_some(e.span()), span: e.span(), }), ) }); Some( directive .clone() .map(|(scrutinee, pat)| { ( Spanned { inner: CfgConditional::Single(scrutinee.inner), span: scrutinee.span, }, pat, ) }) .boxed(), ) } } impl CfgPattern<'_> { fn matches(&self, scrutinee: &ProcessedCfgConditional, ctx: &mut ParseContext) -> bool { use ProcessedCfgConditional as PCC; match (self, scrutinee) { (Self::Tuple(pats, _), PCC::Single(_)) if pats.len() == 1 => { let pat = pats.iter().last().unwrap(); // tuple is actually single pat.matches(scrutinee, ctx) } (Self::Single(pat, _), PCC::Tuple(scrutinees)) if scrutinees.len() == 1 => { let scrutinee = scrutinees.iter().last().unwrap(); // tuple is actually single pat.matches(scrutinee) } (Self::Single(CfgPatternItem::Empty, _), PCC::Tuple(_)) => { // empty pattern matches anything true } (Self::Tuple(_, span), PCC::Single(_)) => { ctx.add_error_str( "Can not match tuple pattern against a single cfg value.", *span, ); false } ( Self::Single(_, span) | Self::Not(_, span) | Self::And(_, span) | Self::Or(_, span) | Self::Grouped(_, span), PCC::Tuple(_), ) => { ctx.add_error_str( "Can not match single pattern against multiple cfg values.", *span, ); false } (Self::Tuple(pats, span), PCC::Tuple(scrutinees)) => { if scrutinees.len() != pats.len() { ctx.add_error_str( "Number of patterns and number of scrutinees do not match.", *span, ); false } else { pats.iter() .zip(scrutinees.iter()) .all(|(pat, scrutinee)| pat.matches(&PCC::Single(scrutinee.clone()), ctx)) } } (Self::Single(pat, _), PCC::Single(scrutinee)) => pat.matches(scrutinee), (Self::Grouped(pat, _), PCC::Single(_)) => pat.matches(scrutinee, ctx), (Self::Not(pat, _), PCC::Single(_)) => !pat.matches(scrutinee, ctx), (Self::And(pats, _), PCC::Single(_)) => { pats.iter().all(|pat| pat.matches(scrutinee, ctx)) } (Self::Or(pats, _), PCC::Single(_)) => { pats.iter().any(|pat| pat.matches(scrutinee, ctx)) } } } } impl CfgPatternItem<'_> { fn matches(&self, scrutinee: &ProcessedCfgScrutinee) -> bool { use ProcessedCfgScrutinee as PCS; match self { Self::Empty => true, Self::Some => !matches!(scrutinee, PCS::Empty), Self::None => matches!(scrutinee, PCS::Empty), Self::Str(v) => match &scrutinee { PCS::Empty | PCS::Some => false, PCS::Values(values) => values.iter().any(|value| value == v), }, Self::Number(n) => match &scrutinee { PCS::Empty | PCS::Some => false, PCS::Values(values) => values .iter() .any(|value| value.parse::().map(|v| v == *n).unwrap_or(false)), }, } } } ================================================ FILE: zngur-parser/src/conditional.rs ================================================ use crate::{BoxedZngParser, ParseContext, Span, Spanned, Token, ZngParser, spanned}; use chumsky::prelude::*; /// a type that can be matched against a Pattern pub trait Matchable: core::fmt::Debug + Clone + PartialEq + Eq { /// A pattern type to match Self against type Pattern: MatchPattern; /// compare self to `Pattern` fn eval(&self, pattern: &Self::Pattern, ctx: &mut ParseContext) -> bool; } /// a type that can be matched against a Pattern pub(crate) trait MatchableParse<'src>: Matchable { /// return a parser for the item as it would appear in an `#if` or `#match` statement fn parser() -> impl ZngParser<'src, Self>; /// return an optional combined parser for use in `#if` statements #[allow(clippy::complexity)] fn combined() -> Option, Spanned<::Pattern>)>> { None } } pub trait MatchPattern: core::fmt::Debug + Clone + PartialEq + Eq { /// a pattern that matches the existence of "some" value fn default_some(span: crate::Span) -> Self; } /// a Pattern that can be matched against pub(crate) trait MatchPatternParse<'src>: MatchPattern { /// return a parser for for the pattern fn parser() -> impl ZngParser<'src, Self>; } pub trait BodyItem: core::fmt::Debug + Clone + PartialEq + Eq { /// The type Self turns into when added into the Spec type Processed; /// Transform self into `Processed` type fn process(self, ctx: &mut ParseContext) -> Self::Processed; } /// a type that hold the body of a conditional statement pub trait ConditionBody: core::fmt::Debug { /// the pattern that guards this body fn pattern(&self) -> &Pattern; } /// a trait that marks the Cardinality of items inside a body (One? or Many?) pub trait ConditionBodyCardinality: core::fmt::Debug + Clone + PartialEq + Eq { /// the type that hold the body of the conditional statement type Body: ConditionBody + Clone + PartialEq + Eq; /// the type that holds the items in the body of a conditional statement type Block: core::fmt::Debug + Clone + PartialEq + Eq + IntoIterator>; /// the type that hold the items of a passing body type EvalResult: IntoIterator::Processed>>; /// transform a single item into Self::Block fn single_to_block(item: Spanned) -> Self::Block; /// create a new empty Self::Bock fn empty_block() -> Self::Block; /// create a new Self::Body from a block and the pattern that guards it fn new_body( pattern: Spanned, block: Self::Block, ) -> Self::Body; /// transform a block into it's processed result fn pass_block(block: &Self::Block, ctx: &mut ParseContext) -> Self::EvalResult; /// transform a body into it's processed result fn pass_body( body: &Self::Body, ctx: &mut ParseContext, ) -> Self::EvalResult; } /// a trait for a conditional item in a parsed spec pub trait ConditionalItem> { /// Evaluate the statement and produce resulting items of the first arm that passes fn eval(&self, ctx: &mut ParseContext) -> Option; } /// a body of a conditional statement that holds 0..N Items #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConditionBodyMany { pub pattern: Spanned, pub block: Vec>, } /// a body of a conditional statement that holds 0..1 items #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConditionBodySingle { pub pattern: Spanned, pub block: Option>, } impl ConditionBody for ConditionBodyMany { fn pattern(&self) -> &Pattern { &self.pattern.inner } } impl ConditionBody for ConditionBodySingle { fn pattern(&self) -> &Pattern { &self.pattern.inner } } /// Marker type for a conditional statement that contextually only accepts one item #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct SingleItem; impl ConditionBodyCardinality for SingleItem { type Body = ConditionBodySingle; type Block = Option>; type EvalResult = Option::Processed>>; fn single_to_block(item: Spanned) -> Self::Block { Some(item) } fn empty_block() -> Self::Block { None } fn new_body( pattern: Spanned, block: Self::Block, ) -> Self::Body { ConditionBodySingle { pattern, block } } fn pass_block(block: &Self::Block, ctx: &mut ParseContext) -> Self::EvalResult { block.clone().map(|item| { let span = item.span; Spanned { span, inner: item.inner.process(ctx), } }) } fn pass_body( body: &Self::Body, ctx: &mut ParseContext, ) -> Self::EvalResult { Self::pass_block(&body.block, ctx) } } /// Marker type for a conditional statement that contextually accepts any number of items #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct NItems; impl ConditionBodyCardinality for NItems { type Body = ConditionBodyMany; type Block = Vec>; type EvalResult = Vec::Processed>>; fn single_to_block(item: Spanned) -> Self::Block { vec![item] } fn empty_block() -> Self::Block { Vec::new() } fn new_body( pattern: Spanned, block: Self::Block, ) -> Self::Body { ConditionBodyMany { pattern, block } } fn pass_block(block: &Self::Block, ctx: &mut ParseContext) -> Self::EvalResult { block .iter() .cloned() .map(|item| { let span = item.span; Spanned { span, inner: item.inner.process(ctx), } }) .collect() } fn pass_body( body: &Self::Body, ctx: &mut ParseContext, ) -> Self::EvalResult { Self::pass_block(&body.block, ctx) } } /// a guard for an #if statement #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConditionGuard { Single { scrutinee: Spanned, pattern: Spanned<::Pattern>, span: Span, }, And(Vec>, Span), Or(Vec>, Span), Not(Box>, Span), Grouped(Box>, Span), } impl ConditionGuard { fn eval(&self, ctx: &mut ParseContext) -> bool { match self { Self::Single { scrutinee, pattern, .. } => scrutinee.inner.eval(&pattern.inner, ctx), Self::And(items, _) => items.iter().all(|item| item.eval(ctx)), Self::Or(items, _) => items.iter().any(|item| item.eval(ctx)), Self::Not(item, _) => !item.eval(ctx), Self::Grouped(item, _) => item.eval(ctx), } } } /// a branch of an `#if` statement #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConditionBranch< Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality, > { pub guard: ConditionGuard, pub block: >::Block, } /// a complete `#if {} #else if #else {}` statement #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConditionIf< Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality, > { pub arms: Vec>, pub fallback: Option, } /// a `#match` statement #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConditionMatch< Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality, > { pub scrutinee: Spanned, pub arms: Vec< Spanned< >::Body< ::Pattern, >, >, >, } impl> ConditionalItem for ConditionBranch { fn eval( &self, ctx: &mut ParseContext, ) -> Option<>::EvalResult> { if self.guard.eval(ctx) { return Some(>::pass_block( &self.block, ctx, )); } None } } impl> ConditionalItem for ConditionMatch { fn eval( &self, ctx: &mut ParseContext, ) -> Option<>::EvalResult> { for arm in &self.arms { let pattern = arm.inner.pattern(); if self.scrutinee.inner.eval(pattern, ctx) { return Some(>::pass_body( &arm.inner, ctx, )); } } None } } impl> ConditionalItem for ConditionIf { fn eval( &self, ctx: &mut ParseContext, ) -> Option<>::EvalResult> { for arm in &self.arms { if let Some(result) = arm.eval(ctx) { return Some(result); } } if let Some(fallback) = &self.fallback { return Some(>::pass_block( fallback, ctx, )); } None } } /// a conditional item behind an if or match statement #[derive(Debug, Clone, PartialEq, Eq)] pub enum Condition< Scrutinee: Matchable, Item: BodyItem, Cardinality: ConditionBodyCardinality, > { If(ConditionIf), Match(ConditionMatch), } impl> ConditionalItem for Condition { fn eval( &self, ctx: &mut ParseContext, ) -> Option<>::EvalResult> { match self { Self::If(item) => item.eval(ctx), Self::Match(item) => item.eval(ctx), } } } /// a trait that helps build combined parsers for ConditionalItem's that accept `#if {} #else {}` or `#match` pub trait Conditional<'src, Item: BodyItem, Cardinality: ConditionBodyCardinality> { type Scrutinee: MatchableParse<'src>; fn if_parser( item_parser: impl ZngParser<'src, Item> + 'src, ) -> BoxedZngParser<'src, Condition>; fn match_parser( item_parser: impl ZngParser<'src, Item> + 'src, ) -> BoxedZngParser<'src, Condition>; } /// a parser for a block used in a SingleItemBody pub fn block_for_single<'src, Item: BodyItem>( item_parser: impl ZngParser<'src, Item>, ) -> impl ZngParser<'src, Option>> { spanned(item_parser) .repeated() .at_most(1) .collect::>() .map(|items| { // should be 1 or zero because of `.at_most(1)` items.into_iter().next() }) } /// a parser for a block used in a ManyItemBody pub fn block_for_many<'src, Item: BodyItem>( item_parser: impl ZngParser<'src, Item>, ) -> impl ZngParser<'src, Vec>> { spanned(item_parser).repeated().collect::>() } /// parser for a guarded block used in `#if` pub fn guarded_block< 'src, Scrutinee: MatchableParse<'src> + 'src, Item: BodyItem, Cardinality: ConditionBodyCardinality, >( block: impl ZngParser<'src, Cardinality::Block>, ) -> impl ZngParser<'src, ConditionBranch> where ::Pattern: MatchPatternParse<'src>, { let guard = recursive(|guard| { let single = if let Some(combined) = >::combined() { combined } else { spanned(>::parser()) .then( just(Token::Eq) .ignore_then(spanned( <::Pattern as MatchPatternParse<'src>>::parser( ), )) .or_not() .map_with(|pat, e| { pat.unwrap_or_else(|| Spanned { inner: ::Pattern::default_some(e.span()), span: e.span(), }) }), ) .boxed() } .map_with(|(scrutinee, pattern), e| ConditionGuard::Single { scrutinee, pattern, span: e.span(), }) .or( spanned(guard.delimited_by(just(Token::ParenOpen), just(Token::ParenClose))) .map(|item| ConditionGuard::Grouped(Box::new(item.inner), item.span)), ); let not_pat = just(Token::Bang) .repeated() .foldr_with(single, |_op, rhs, e| { ConditionGuard::Not(Box::new(rhs), e.span()) }); let and_pat = not_pat.clone().foldl_with( just([Token::And, Token::And]) .ignore_then(not_pat) .repeated(), |lhs, rhs, e| match lhs { ConditionGuard::And(mut items, _span) => { items.push(rhs); ConditionGuard::And(items, e.span()) } _ => ConditionGuard::And(vec![lhs, rhs], e.span()), }, ); // or pat and_pat.clone().foldl_with( just([Token::Pipe, Token::Pipe]) .ignore_then(and_pat) .repeated(), |lhs, rhs, e| match lhs { ConditionGuard::Or(mut items, _span) => { items.push(rhs); ConditionGuard::Or(items, e.span()) } _ => ConditionGuard::Or(vec![lhs, rhs], e.span()), }, ) }); guard .then(block.delimited_by(just(Token::BraceOpen), just(Token::BraceClose))) .map(move |(guard, block)| ConditionBranch { guard, block }) } /// parser for an `#if {} #else if {} #else {}` statement pub fn if_stmnt< 'src, Scrutinee: MatchableParse<'src>, Item: BodyItem, Cardinality: ConditionBodyCardinality, >( guard: impl ZngParser<'src, ConditionBranch>, fallback: impl ZngParser<'src, Cardinality::Block>, ) -> impl ZngParser<'src, ConditionIf> where ::Pattern: MatchPatternParse<'src>, { just([Token::Sharp, Token::KwIf]) .ignore_then(guard.clone()) .then( just([Token::Sharp, Token::KwElse, Token::KwIf]) .ignore_then(guard) .repeated() .collect::>() .or_not(), ) .then( just([Token::Sharp, Token::KwElse]) .ignore_then(fallback.delimited_by(just(Token::BraceOpen), just(Token::BraceClose))) .or_not(), ) .map(|((if_block, else_if_blocks), else_block)| { let mut arms: Vec> = vec![if_block]; arms.extend(else_if_blocks.unwrap_or_default()); ConditionIf { arms, fallback: else_block, } }) } /// a parser for the arm of a `#match` statement fn match_arm< 'src, Scrutinee: MatchableParse<'src>, Item: BodyItem, Cardinality: ConditionBodyCardinality, >( block: impl ZngParser<'src, Cardinality::Block>, item_parser: impl ZngParser<'src, Item>, ) -> impl ZngParser< 'src, >::Body<::Pattern>, > where ::Pattern: MatchPatternParse<'src>, { let arm_choices = ( block.delimited_by(just(Token::BraceOpen), just(Token::BraceClose)), spanned(item_parser).map(>::single_to_block), just([Token::BraceOpen, Token::BraceClose]) .map(|_| >::empty_block()), ); spanned(<::Pattern as MatchPatternParse< 'src, >>::parser()) .then(just(Token::ArrowArm).ignore_then(choice(arm_choices))) .map(|(pattern, block)| { >::new_body(pattern, block) }) } /// a parser for a `#match` statement fn match_stmt< 'src, Scrutinee: MatchableParse<'src>, Item: BodyItem, Cardinality: ConditionBodyCardinality, >( block: impl ZngParser<'src, Cardinality::Block>, item_parser: impl ZngParser<'src, Item>, ) -> impl ZngParser<'src, ConditionMatch> where ::Pattern: MatchPatternParse<'src>, { let arm = match_arm::(block, item_parser); just([Token::Sharp, Token::KwMatch]) .ignore_then(spanned(::parser())) .then( spanned(arm) .separated_by(just(Token::Comma).or_not()) .allow_trailing() .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)), ) .map(|(scrutinee, arms)| ConditionMatch { scrutinee, arms }) } pub fn conditional_item< 'src, Item: BodyItem, Cond: Conditional<'src, Item, Cardinality>, Cardinality: ConditionBodyCardinality, >( item_parser: impl ZngParser<'src, Item> + 'src, ) -> impl ZngParser< 'src, Condition<>::Scrutinee, Item, Cardinality>, > { let if_parser = >::if_parser(item_parser.clone()).try_map_with(|match_, e| { if !e.state().unstable_features.cfg_if { Err(Rich::custom(e.span(), "`#if` statements are unstable. Enable them by using `#unstable(cfg_if)` at the top of the file.")) } else { Ok(match_) } }); let match_parser = >::match_parser(item_parser).try_map_with(|match_, e| { if !e.state().unstable_features.cfg_match { Err(Rich::custom(e.span(), "`#match` statements are unstable. Enable them by using `#unstable(cfg_match)` at the top of the file.")) } else { Ok(match_) } }); choice((if_parser, match_parser)) } impl<'src, Scrutinee: MatchableParse<'src> + 'src, Item: BodyItem + 'src> Conditional<'src, Item, SingleItem> for Scrutinee where ::Pattern: MatchPatternParse<'src>, { type Scrutinee = Scrutinee; fn if_parser( item_parser: impl ZngParser<'src, Item> + 'src, ) -> BoxedZngParser<'src, Condition> { let block = block_for_single::(item_parser); let guard = guarded_block::(block.clone()); if_stmnt::(guard, block) .map(|item| Condition::::If(item)) .boxed() } fn match_parser( item_parser: impl ZngParser<'src, Item> + 'src, ) -> BoxedZngParser<'src, Condition> { let block = block_for_single::(item_parser.clone()); match_stmt::(block, item_parser) .map(|item| Condition::::Match(item)) .boxed() } } impl<'src, Scrutinee: MatchableParse<'src> + 'src, Item: BodyItem + 'src> Conditional<'src, Item, NItems> for Scrutinee where ::Pattern: MatchPatternParse<'src>, { type Scrutinee = Scrutinee; fn if_parser( item_parser: impl ZngParser<'src, Item> + 'src, ) -> BoxedZngParser<'src, Condition> { let block = block_for_many::(item_parser); let guard = guarded_block::(block.clone()); if_stmnt::(guard, block) .map(|item| Condition::::If(item)) .boxed() } fn match_parser( item_parser: impl ZngParser<'src, Item> + 'src, ) -> BoxedZngParser<'src, Condition> { let block = block_for_many::(item_parser.clone()); match_stmt::(block, item_parser) .map(|item| Condition::::Match(item)) .boxed() } } ================================================ FILE: zngur-parser/src/lib.rs ================================================ use std::{collections::HashMap, fmt::Display, path::Component}; #[cfg(not(test))] use std::process::exit; use ariadne::{Color, Label, Report, ReportKind, sources}; use chumsky::prelude::*; use itertools::{Either, Itertools}; use zngur_def::{ AdditionalIncludes, ConvertPanicToException, CppRef, CppValue, Import, LayoutPolicy, Merge, MergeFailure, ModuleImport, Mutability, PrimitiveRustType, RustPathAndGenerics, RustTrait, RustType, ZngurConstructor, ZngurExternCppFn, ZngurExternCppImpl, ZngurField, ZngurFn, ZngurMethod, ZngurMethodDetails, ZngurMethodReceiver, ZngurSpec, ZngurTrait, ZngurType, ZngurWellknownTrait, }; pub type Span = SimpleSpan; /// Result of parsing a .zng file, containing both the spec and the list of all processed files. #[derive(Debug)] pub struct ParseResult { /// The parsed Zngur specification pub spec: ZngurSpec, /// All .zng files that were processed (main file + transitive imports) pub processed_files: Vec, } #[cfg(test)] mod tests; pub mod cfg; mod conditional; use crate::{ cfg::{CfgConditional, RustCfgProvider}, conditional::{Condition, ConditionalItem, NItems, conditional_item}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Spanned { inner: T, span: Span, } type ParserInput<'a> = chumsky::input::MappedInput< Token<'a>, Span, &'a [(Token<'a>, Span)], Box< dyn for<'x> Fn( &'x (Token<'_>, chumsky::span::SimpleSpan), ) -> (&'x Token<'x>, &'x SimpleSpan), >, >; #[derive(Default)] pub struct UnstableFeatures { pub cfg_match: bool, pub cfg_if: bool, } #[derive(Default)] pub struct ZngParserState { pub unstable_features: UnstableFeatures, } type ZngParserExtra<'a> = extra::Full, Span>, extra::SimpleState, ()>; type BoxedZngParser<'a, Item> = chumsky::Boxed<'a, 'a, ParserInput<'a>, Item, ZngParserExtra<'a>>; /// Effective trait alias for verbose chumsky Parser Trait pub(crate) trait ZngParser<'a, Item>: Parser<'a, ParserInput<'a>, Item, ZngParserExtra<'a>> + Clone { } impl<'a, T, Item> ZngParser<'a, Item> for T where T: Parser<'a, ParserInput<'a>, Item, ZngParserExtra<'a>> + Clone { } #[derive(Debug)] pub struct ParsedZngFile<'a>(Vec>); #[derive(Debug)] pub struct ProcessedZngFile<'a> { aliases: Vec>, items: Vec>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum ParsedPathStart { Absolute, Relative, Crate, } #[derive(Debug, Clone, PartialEq, Eq)] struct ParsedPath<'a> { start: ParsedPathStart, segments: Vec<&'a str>, span: Span, } #[derive(Debug, Clone)] struct Scope<'a> { aliases: Vec>, base: Vec, } impl<'a> Scope<'a> { /// Create a new root scope containing the specified aliases. fn new_root(aliases: Vec>) -> Scope<'a> { Scope { aliases, base: Vec::new(), } } /// Resolve a path according to the current scope. fn resolve_path(&self, path: ParsedPath<'a>) -> Vec { // Check to see if the path refers to an alias: if let Some(expanded_alias) = self .aliases .iter() .find_map(|alias| alias.expand(&path, &self.base)) { expanded_alias } else { path.to_zngur(&self.base) } } /// Create a fully-qualified path relative to this scope's base path. fn simple_relative_path(&self, relative_item_name: &str) -> Vec { self.base .iter() .cloned() .chain(Some(relative_item_name.to_string())) .collect() } fn sub_scope(&self, new_aliases: &[ParsedAlias<'a>], nested_path: ParsedPath<'a>) -> Scope<'_> { let base = nested_path.to_zngur(&self.base); let mut mod_aliases = new_aliases.to_vec(); mod_aliases.extend_from_slice(&self.aliases); Scope { aliases: mod_aliases, base, } } } impl ParsedPath<'_> { fn to_zngur(self, base: &[String]) -> Vec { match self.start { ParsedPathStart::Absolute => self.segments.into_iter().map(|x| x.to_owned()).collect(), ParsedPathStart::Relative => base .iter() .map(|x| x.as_str()) .chain(self.segments) .map(|x| x.to_owned()) .collect(), ParsedPathStart::Crate => ["crate"] .into_iter() .chain(self.segments) .map(|x| x.to_owned()) .collect(), } } fn matches_alias(&self, alias: &ParsedAlias<'_>) -> bool { match self.start { ParsedPathStart::Absolute | ParsedPathStart::Crate => false, ParsedPathStart::Relative => self .segments .first() .is_some_and(|part| *part == alias.name), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParsedAlias<'a> { name: &'a str, path: ParsedPath<'a>, span: Span, } impl ParsedAlias<'_> { fn expand(&self, path: &ParsedPath<'_>, base: &[String]) -> Option> { if path.matches_alias(self) { match self.path.start { ParsedPathStart::Absolute => Some( self.path .segments .iter() .chain(path.segments.iter().skip(1)) .map(|seg| (*seg).to_owned()) .collect(), ), ParsedPathStart::Crate => Some( ["crate"] .into_iter() .chain(self.path.segments.iter().cloned()) .chain(path.segments.iter().skip(1).cloned()) .map(|seg| (*seg).to_owned()) .collect(), ), ParsedPathStart::Relative => Some( base.iter() .map(|x| x.as_str()) .chain(self.path.segments.iter().cloned()) .chain(path.segments.iter().skip(1).cloned()) .map(|seg| (*seg).to_owned()) .collect(), ), } } else { None } } } #[derive(Debug, Clone, PartialEq, Eq)] struct ParsedImportPath { path: std::path::PathBuf, span: Span, } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedItem<'a> { ConvertPanicToException(Span), CppAdditionalInclude(&'a str), UnstableFeature(&'a str), Mod { path: ParsedPath<'a>, items: Vec>, }, Type { ty: Spanned>, items: Vec>>, }, Trait { tr: Spanned>, methods: Vec>, }, Fn(Spanned>), ExternCpp(Vec>), Alias(ParsedAlias<'a>), Import(ParsedImportPath), ModuleImport { path: std::path::PathBuf, span: Span, }, MatchOnCfg(Condition, ParsedItem<'a>, NItems>), } #[derive(Debug, Clone, PartialEq, Eq)] enum ProcessedItem<'a> { ConvertPanicToException(Span), CppAdditionalInclude(&'a str), Mod { path: ParsedPath<'a>, items: Vec>, aliases: Vec>, }, Type { ty: Spanned>, items: Vec>>, }, Trait { tr: Spanned>, methods: Vec>, }, Fn(Spanned>), ExternCpp(Vec>), Import(ParsedImportPath), ModuleImport { path: std::path::PathBuf, span: Span, }, } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedExternCppItem<'a> { Function(Spanned>), Impl { tr: Option>, ty: Spanned>, methods: Vec>, }, } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedConstructorArgs<'a> { Unit, Tuple(Vec>), Named(Vec<(&'a str, ParsedRustType<'a>)>), } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedLayoutPolicy<'a> { StackAllocated(Vec<(Spanned<&'a str>, usize)>), Conservative(Vec<(Spanned<&'a str>, usize)>), HeapAllocated, OnlyByRef, } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedTypeItem<'a> { Layout(Span, ParsedLayoutPolicy<'a>), Traits(Vec>), Constructor { name: Option<&'a str>, args: ParsedConstructorArgs<'a>, }, Field { name: String, ty: ParsedRustType<'a>, offset: Option, }, Method { data: ParsedMethod<'a>, use_path: Option>, deref: Option>, }, CppValue { field: &'a str, cpp_type: &'a str, }, CppRef { cpp_type: &'a str, }, MatchOnCfg(Condition, ParsedTypeItem<'a>, NItems>), } #[derive(Debug, Clone, PartialEq, Eq)] struct ParsedMethod<'a> { name: &'a str, receiver: ZngurMethodReceiver, generics: Vec>, inputs: Vec>, output: ParsedRustType<'a>, } impl ParsedMethod<'_> { fn to_zngur(self, scope: &Scope<'_>) -> ZngurMethod { ZngurMethod { name: self.name.to_owned(), generics: self .generics .into_iter() .map(|x| x.to_zngur(scope)) .collect(), receiver: self.receiver, inputs: self.inputs.into_iter().map(|x| x.to_zngur(scope)).collect(), output: self.output.to_zngur(scope), } } } fn checked_merge(src: T, dst: &mut U, span: Span, ctx: &mut ParseContext) where T: Merge, { match src.merge(dst) { Ok(()) => {} Err(e) => match e { MergeFailure::Conflict(s) => { ctx.add_error_str(&s, span); } }, } } impl ProcessedItem<'_> { fn add_to_zngur_spec(self, r: &mut ZngurSpec, scope: &Scope<'_>, ctx: &mut ParseContext) { match self { ProcessedItem::Mod { path, items, aliases, } => { let sub_scope = scope.sub_scope(&aliases, path); for item in items { item.add_to_zngur_spec(r, &sub_scope, ctx); } } ProcessedItem::Import(path) => { if path.path.is_absolute() { ctx.add_error_str("Absolute paths imports are not supported.", path.span) } match path.path.components().next() { Some(Component::CurDir) | Some(Component::ParentDir) => { r.imports.push(Import(path.path)); } _ => ctx.add_error_str( "Module import is not supported. Use a relative path instead.", path.span, ), } } ProcessedItem::ModuleImport { path, span: _ } => { r.imported_modules.push(ModuleImport { path: path.clone() }); } ProcessedItem::Type { ty, items } => { if ty.inner == ParsedRustType::Tuple(vec![]) { // We add unit type implicitly. ctx.add_error_str( "Unit type is declared implicitly. Remove this entirely.", ty.span, ); } let mut methods = vec![]; let mut constructors = vec![]; let mut fields = vec![]; let mut wellknown_traits = vec![]; let mut layout = None; let mut layout_span = None; let mut cpp_value = None; let mut cpp_ref = None; let mut to_process = items; to_process.reverse(); // create a stack of items to process while let Some(item) = to_process.pop() { let item_span = item.span; let item = item.inner; match item { ParsedTypeItem::Layout(span, p) => { let mut check_size_align = |props: Vec<(Spanned<&str>, usize)>| { let mut size = None; let mut align = None; for (key, value) in props { match key.inner { "size" => size = Some(value), "align" => align = Some(value), _ => ctx.add_error_str("Unknown property", key.span), } } let Some(size) = size else { ctx.add_error_str( "Size is not declared for this type", ty.span, ); return None; }; let Some(align) = align else { ctx.add_error_str( "Align is not declared for this type", ty.span, ); return None; }; Some((size, align)) }; layout = Some(match p { ParsedLayoutPolicy::StackAllocated(p) => { let Some((size, align)) = check_size_align(p) else { continue; }; LayoutPolicy::StackAllocated { size, align } } ParsedLayoutPolicy::Conservative(p) => { let Some((size, align)) = check_size_align(p) else { continue; }; LayoutPolicy::Conservative { size, align } } ParsedLayoutPolicy::HeapAllocated => LayoutPolicy::HeapAllocated, ParsedLayoutPolicy::OnlyByRef => LayoutPolicy::OnlyByRef, }); match layout_span { Some(_) => { ctx.add_error_str("Duplicate layout policy found", span); } None => layout_span = Some(span), } } ParsedTypeItem::Traits(tr) => { wellknown_traits.extend(tr); } ParsedTypeItem::Constructor { name, args } => { constructors.push(ZngurConstructor { name: name.map(|x| x.to_owned()), inputs: match args { ParsedConstructorArgs::Unit => vec![], ParsedConstructorArgs::Tuple(t) => t .into_iter() .enumerate() .map(|(i, t)| (i.to_string(), t.to_zngur(scope))) .collect(), ParsedConstructorArgs::Named(t) => t .into_iter() .map(|(i, t)| (i.to_owned(), t.to_zngur(scope))) .collect(), }, }) } ParsedTypeItem::Field { name, ty, offset } => { fields.push(ZngurField { name: name.to_owned(), ty: ty.to_zngur(scope), offset, }); } ParsedTypeItem::Method { data, use_path, deref, } => { let deref = deref.and_then(|x| { let deref_type = x.to_zngur(scope); let receiver_mutability = match data.receiver { ZngurMethodReceiver::Ref(mutability) => mutability, ZngurMethodReceiver::Static | ZngurMethodReceiver::Move => { ctx.add_error_str( "Deref needs reference receiver", item_span, ); return None; } }; Some((deref_type, receiver_mutability)) }); methods.push(ZngurMethodDetails { data: data.to_zngur(scope), use_path: use_path.map(|x| scope.resolve_path(x)), deref, }); } ParsedTypeItem::CppValue { field, cpp_type } => { cpp_value = Some(CppValue(field.to_owned(), cpp_type.to_owned())); } ParsedTypeItem::CppRef { cpp_type } => { match layout_span { Some(span) => { ctx.add_error_str("Duplicate layout policy found", span); continue; } None => { layout = Some(LayoutPolicy::ZERO_SIZED_TYPE); layout_span = Some(item_span); } } cpp_ref = Some(CppRef(cpp_type.to_owned())); } ParsedTypeItem::MatchOnCfg(match_) => { let result = match_.eval(ctx); if let Some(result) = result { to_process.extend(result); } } } } let is_unsized = wellknown_traits .iter() .find(|x| x.inner == ZngurWellknownTrait::Unsized) .cloned(); let is_copy = wellknown_traits .iter() .find(|x| x.inner == ZngurWellknownTrait::Copy) .cloned(); let mut wt = wellknown_traits .into_iter() .map(|x| x.inner) .collect::>(); if is_copy.is_none() && is_unsized.is_none() { wt.push(ZngurWellknownTrait::Drop); } if let Some(is_unsized) = is_unsized { if let Some(span) = layout_span { ctx.add_report( Report::build( ReportKind::Error, ctx.filename().to_string(), span.start, ) .with_message("Duplicate layout policy found for unsized type.") .with_label( Label::new((ctx.filename().to_string(), span.start..span.end)) .with_message( "Unsized types have implicit layout policy, remove this.", ) .with_color(Color::Red), ) .with_label( Label::new(( ctx.filename().to_string(), is_unsized.span.start..is_unsized.span.end, )) .with_message("Type declared as unsized here.") .with_color(Color::Blue), ) .finish(), ) } layout = Some(LayoutPolicy::OnlyByRef); } if let Some(layout) = layout { checked_merge( ZngurType { ty: ty.inner.to_zngur(scope), layout, methods, wellknown_traits: wt, constructors, fields, cpp_value, cpp_ref, }, r, ty.span, ctx, ); } else { ctx.add_error_str( "No layout policy found for this type. \ Use one of `#layout(size = X, align = Y)`, `#heap_allocated` or `#only_by_ref`.", ty.span, ); }; } ProcessedItem::Trait { tr, methods } => { checked_merge( ZngurTrait { tr: tr.inner.to_zngur(scope), methods: methods.into_iter().map(|m| m.to_zngur(scope)).collect(), }, r, tr.span, ctx, ); } ProcessedItem::Fn(f) => { let method = f.inner.to_zngur(scope); checked_merge( ZngurFn { path: RustPathAndGenerics { path: scope.simple_relative_path(&method.name), generics: method.generics, named_generics: vec![], }, inputs: method.inputs, output: method.output, }, r, f.span, ctx, ); } ProcessedItem::ExternCpp(items) => { for item in items { match item { ParsedExternCppItem::Function(method) => { let span = method.span; let method = method.inner.to_zngur(scope); checked_merge( ZngurExternCppFn { name: method.name.to_string(), inputs: method.inputs, output: method.output, }, r, span, ctx, ); } ParsedExternCppItem::Impl { tr, ty, methods } => { checked_merge( ZngurExternCppImpl { tr: tr.map(|x| x.to_zngur(scope)), ty: ty.inner.to_zngur(scope), methods: methods .into_iter() .map(|x| x.to_zngur(scope)) .collect(), }, r, ty.span, ctx, ); } } } } ProcessedItem::CppAdditionalInclude(s) => { match AdditionalIncludes(s.to_owned()).merge(r) { Ok(()) => {} Err(_) => { unreachable!() // For now, additional includes can't have conflicts. } } } ProcessedItem::ConvertPanicToException(span) => { if ctx.depth > 0 { ctx.add_error_str( "Using `#convert_panic_to_exception` in imported zngur files is not supported. This directive can only be used in the main zngur file.", span, ); return; } match ConvertPanicToException(true).merge(r) { Ok(()) => {} Err(_) => { unreachable!() // For now, CPtE also can't have conflicts. } } } } } } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedRustType<'a> { Primitive(PrimitiveRustType), Ref(Mutability, Box>), Raw(Mutability, Box>), Boxed(Box>), Slice(Box>), Dyn(ParsedRustTrait<'a>, Vec<&'a str>), Impl(ParsedRustTrait<'a>, Vec<&'a str>), Tuple(Vec>), Adt(ParsedRustPathAndGenerics<'a>), } impl ParsedRustType<'_> { fn to_zngur(self, scope: &Scope<'_>) -> RustType { match self { ParsedRustType::Primitive(s) => RustType::Primitive(s), ParsedRustType::Ref(m, s) => RustType::Ref(m, Box::new(s.to_zngur(scope))), ParsedRustType::Raw(m, s) => RustType::Raw(m, Box::new(s.to_zngur(scope))), ParsedRustType::Boxed(s) => RustType::Boxed(Box::new(s.to_zngur(scope))), ParsedRustType::Slice(s) => RustType::Slice(Box::new(s.to_zngur(scope))), ParsedRustType::Dyn(tr, bounds) => RustType::Dyn( tr.to_zngur(scope), bounds.into_iter().map(|x| x.to_owned()).collect(), ), ParsedRustType::Impl(tr, bounds) => RustType::Impl( tr.to_zngur(scope), bounds.into_iter().map(|x| x.to_owned()).collect(), ), ParsedRustType::Tuple(v) => { RustType::Tuple(v.into_iter().map(|s| s.to_zngur(scope)).collect()) } ParsedRustType::Adt(s) => RustType::Adt(s.to_zngur(scope)), } } } #[derive(Debug, Clone, PartialEq, Eq)] enum ParsedRustTrait<'a> { Normal(ParsedRustPathAndGenerics<'a>), Fn { name: &'a str, inputs: Vec>, output: Box>, }, } impl ParsedRustTrait<'_> { fn to_zngur(self, scope: &Scope<'_>) -> RustTrait { match self { ParsedRustTrait::Normal(s) => RustTrait::Normal(s.to_zngur(scope)), ParsedRustTrait::Fn { name, inputs, output, } => RustTrait::Fn { name: name.to_owned(), inputs: inputs.into_iter().map(|s| s.to_zngur(scope)).collect(), output: Box::new(output.to_zngur(scope)), }, } } } #[derive(Debug, Clone, PartialEq, Eq)] struct ParsedRustPathAndGenerics<'a> { path: ParsedPath<'a>, generics: Vec>, named_generics: Vec<(&'a str, ParsedRustType<'a>)>, } impl ParsedRustPathAndGenerics<'_> { fn to_zngur(self, scope: &Scope<'_>) -> RustPathAndGenerics { RustPathAndGenerics { path: scope.resolve_path(self.path), generics: self .generics .into_iter() .map(|x| x.to_zngur(scope)) .collect(), named_generics: self .named_generics .into_iter() .map(|(name, x)| (name.to_owned(), x.to_zngur(scope))) .collect(), } } } struct ParseContext<'a, 'b> { path: std::path::PathBuf, text: &'a str, depth: usize, reports: Vec)>>, source_cache: std::collections::HashMap, /// All .zng files processed during parsing (main file + imports) processed_files: Vec, cfg_provider: Box, } impl<'a, 'b> ParseContext<'a, 'b> { fn new(path: std::path::PathBuf, text: &'a str, cfg: Box) -> Self { let processed_files = vec![path.clone()]; Self { path, text, depth: 0, reports: Vec::new(), source_cache: HashMap::new(), processed_files, cfg_provider: cfg, } } fn with_depth( path: std::path::PathBuf, text: &'a str, depth: usize, cfg: Box, ) -> Self { let processed_files = vec![path.clone()]; Self { path, text, depth, reports: Vec::new(), source_cache: HashMap::new(), processed_files, cfg_provider: cfg, } } fn filename(&self) -> &str { self.path.file_name().unwrap().to_str().unwrap() } fn add_report(&mut self, report: Report<'b, (String, std::ops::Range)>) { self.reports.push(report); } fn add_errors<'err_src>(&mut self, errs: impl Iterator>) { let filename = self.filename().to_string(); self.reports.extend(errs.map(|e| { Report::build(ReportKind::Error, &filename, e.span().start) .with_message(e.to_string()) .with_label( Label::new((filename.clone(), e.span().into_range())) .with_message(e.reason().to_string()) .with_color(Color::Red), ) .with_labels(e.contexts().map(|(label, span)| { Label::new((filename.clone(), span.into_range())) .with_message(format!("while parsing this {}", label)) .with_color(Color::Yellow) })) .finish() })); } fn add_error_str(&mut self, error: &str, span: Span) { self.add_errors([Rich::custom(span, error)].into_iter()); } fn consume_from(&mut self, mut other: ParseContext<'_, 'b>) { // Always merge processed files, regardless of errors self.processed_files.append(&mut other.processed_files); if other.has_errors() { self.reports.extend(other.reports); self.source_cache.insert(other.path, other.text.to_string()); self.source_cache.extend(other.source_cache); } } fn has_errors(&self) -> bool { !self.reports.is_empty() } #[cfg(test)] fn emit_ariadne_errors(&self) -> ! { let mut r = Vec::::new(); for err in &self.reports { err.write( sources( [(self.filename().to_string(), self.text)] .into_iter() .chain( self.source_cache .iter() .map(|(path, text)| { ( path.file_name().unwrap().to_str().unwrap().to_string(), text.as_str(), ) }) .collect::>() .into_iter(), ), ), &mut r, ) .unwrap(); } std::panic::resume_unwind(Box::new(tests::ErrorText( String::from_utf8(strip_ansi_escapes::strip(r)).unwrap(), ))); } #[cfg(not(test))] fn emit_ariadne_errors(&self) -> ! { for err in &self.reports { err.eprint(sources( [(self.filename().to_string(), self.text)] .into_iter() .chain( self.source_cache .iter() .map(|(path, text)| { ( path.file_name().unwrap().to_str().unwrap().to_string(), text.as_str(), ) }) .collect::>() .into_iter(), ), )) .unwrap(); } exit(101); } fn get_config_provider(&self) -> &dyn RustCfgProvider { self.cfg_provider.as_ref() } } /// A trait for types which can resolve filesystem-like paths relative to a given directory. pub trait ImportResolver { fn resolve_import( &self, cwd: &std::path::Path, relpath: &std::path::Path, ) -> Result; } /// A default implementation of ImportResolver which uses conventional filesystem paths and semantics. struct DefaultImportResolver; impl ImportResolver for DefaultImportResolver { fn resolve_import( &self, cwd: &std::path::Path, relpath: &std::path::Path, ) -> Result { let path = cwd .join(relpath) .canonicalize() .map_err(|e| e.to_string())?; std::fs::read_to_string(path).map_err(|e| e.to_string()) } } impl<'a> ParsedZngFile<'a> { fn parse_into(zngur: &mut ZngurSpec, ctx: &mut ParseContext, resolver: &impl ImportResolver) { let (tokens, errs) = lexer().parse(ctx.text).into_output_errors(); let Some(tokens) = tokens else { ctx.add_errors(errs.into_iter().map(|e| e.map_token(|c| c.to_string()))); ctx.emit_ariadne_errors(); }; let tokens: ParserInput<'_> = tokens.as_slice().map( (ctx.text.len()..ctx.text.len()).into(), Box::new(|(t, s)| (t, s)), ); let (ast, errs) = file_parser() .map_with(|ast, extra| (ast, extra.span())) .parse_with_state(tokens, &mut extra::SimpleState(ZngParserState::default())) .into_output_errors(); let Some(ast) = ast else { ctx.add_errors(errs.into_iter().map(|e| e.map_token(|c| c.to_string()))); ctx.emit_ariadne_errors(); }; let (aliases, items) = partition_parsed_items( ast.0 .0 .into_iter() .map(|item| process_parsed_item(item, ctx)), ); ProcessedZngFile::new(aliases, items).into_zngur_spec(zngur, ctx); if let Some(dirname) = ctx.path.to_owned().parent() { for import in std::mem::take(&mut zngur.imports) { match resolver.resolve_import(dirname, &import.0) { Ok(text) => { let mut nested_ctx = ParseContext::with_depth( dirname.join(&import.0), &text, ctx.depth + 1, ctx.get_config_provider().clone_box(), ); Self::parse_into(zngur, &mut nested_ctx, resolver); ctx.consume_from(nested_ctx); } Err(_) => { // TODO: emit a better error. How should we get a span here? // I'd like to avoid putting a ParsedImportPath in ZngurSpec, and // also not have to pass a filename to add_to_zngur_spec. ctx.add_report( Report::build(ReportKind::Error, ctx.filename(), 0) .with_message(format!( "Import path not found: {}", import.0.display() )) .finish(), ); } } } } } /// Parse a .zng file and return both the spec and list of all processed files. pub fn parse(path: std::path::PathBuf, cfg: Box) -> ParseResult { let mut zngur = ZngurSpec::default(); zngur.rust_cfg.extend(cfg.get_cfg_pairs()); let text = std::fs::read_to_string(&path).unwrap(); let mut ctx = ParseContext::new(path.clone(), &text, cfg.clone_box()); Self::parse_into(&mut zngur, &mut ctx, &DefaultImportResolver); if ctx.has_errors() { // add report of cfg values used ctx.add_report( Report::build( ReportKind::Custom("cfg values", ariadne::Color::Green), path.file_name().unwrap_or_default().to_string_lossy(), 0, ) .with_message( cfg.get_cfg_pairs() .into_iter() .map(|(key, value)| match value { Some(value) => format!("{key}=\"{value}\""), None => key, }) .join("\n") .to_string(), ) .finish(), ); ctx.emit_ariadne_errors(); } ParseResult { spec: zngur, processed_files: ctx.processed_files, } } /// Parse a .zng file from a string. Mainly useful for testing. pub fn parse_str(text: &str, cfg: impl RustCfgProvider + 'static) -> ParseResult { let mut zngur = ZngurSpec::default(); let mut ctx = ParseContext::new(std::path::PathBuf::from("test.zng"), text, Box::new(cfg)); Self::parse_into(&mut zngur, &mut ctx, &DefaultImportResolver); if ctx.has_errors() { ctx.emit_ariadne_errors(); } ParseResult { spec: zngur, processed_files: ctx.processed_files, } } #[cfg(test)] pub(crate) fn parse_str_with_resolver( text: &str, cfg: impl RustCfgProvider + 'static, resolver: &impl ImportResolver, ) -> ParseResult { let mut zngur = ZngurSpec::default(); let mut ctx = ParseContext::new(std::path::PathBuf::from("test.zng"), text, Box::new(cfg)); Self::parse_into(&mut zngur, &mut ctx, resolver); if ctx.has_errors() { ctx.emit_ariadne_errors(); } ParseResult { spec: zngur, processed_files: ctx.processed_files, } } } pub(crate) enum ProcessedItemOrAlias<'a> { Ignore, Processed(ProcessedItem<'a>), Alias(ParsedAlias<'a>), ChildItems(Vec>), } fn process_parsed_item<'a>( item: ParsedItem<'a>, ctx: &mut ParseContext, ) -> ProcessedItemOrAlias<'a> { use ProcessedItemOrAlias as Ret; match item { ParsedItem::Alias(alias) => Ret::Alias(alias), ParsedItem::ConvertPanicToException(span) => { Ret::Processed(ProcessedItem::ConvertPanicToException(span)) } ParsedItem::UnstableFeature(_) => { // ignore Ret::Ignore } ParsedItem::CppAdditionalInclude(inc) => { Ret::Processed(ProcessedItem::CppAdditionalInclude(inc)) } ParsedItem::Mod { path, items } => { let (aliases, items) = partition_parsed_items( items.into_iter().map(|item| process_parsed_item(item, ctx)), ); Ret::Processed(ProcessedItem::Mod { path, items, aliases, }) } ParsedItem::Type { ty, items } => Ret::Processed(ProcessedItem::Type { ty, items }), ParsedItem::Trait { tr, methods } => Ret::Processed(ProcessedItem::Trait { tr, methods }), ParsedItem::Fn(method) => Ret::Processed(ProcessedItem::Fn(method)), ParsedItem::ExternCpp(items) => Ret::Processed(ProcessedItem::ExternCpp(items)), ParsedItem::Import(path) => Ret::Processed(ProcessedItem::Import(path)), ParsedItem::ModuleImport { path, span } => { Ret::Processed(ProcessedItem::ModuleImport { path, span }) } ParsedItem::MatchOnCfg(match_) => Ret::ChildItems( match_ .eval(ctx) .unwrap_or_default() // unwrap or empty .into_iter() .map(|item| item.inner) .collect(), ), } } fn partition_parsed_items<'a>( items: impl IntoIterator>, ) -> (Vec>, Vec>) { let mut aliases = Vec::new(); let mut processed = Vec::new(); for item in items.into_iter() { match item { ProcessedItemOrAlias::Ignore => continue, ProcessedItemOrAlias::Processed(p) => processed.push(p), ProcessedItemOrAlias::Alias(a) => aliases.push(a), ProcessedItemOrAlias::ChildItems(children) => { let (child_aliases, child_items) = partition_parsed_items(children); aliases.extend(child_aliases); processed.extend(child_items); } } } (aliases, processed) } impl<'a> ProcessedZngFile<'a> { fn new(aliases: Vec>, items: Vec>) -> Self { ProcessedZngFile { aliases, items } } fn into_zngur_spec(self, zngur: &mut ZngurSpec, ctx: &mut ParseContext) { let root_scope = Scope::new_root(self.aliases); for item in self.items { item.add_to_zngur_spec(zngur, &root_scope, ctx); } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Token<'a> { Arrow, ArrowArm, AngleOpen, AngleClose, BracketOpen, BracketClose, Colon, ColonColon, ParenOpen, ParenClose, BraceOpen, BraceClose, And, Star, Sharp, Plus, Eq, Question, Comma, Semicolon, Pipe, Underscore, Dot, Bang, KwAs, KwAsync, KwDyn, KwUse, KwFor, KwMod, KwCrate, KwType, KwTrait, KwFn, KwMut, KwConst, KwExtern, KwImpl, KwImport, KwMerge, KwIf, KwElse, KwMatch, Ident(&'a str), Str(&'a str), Number(usize), } impl<'a> Token<'a> { fn ident_or_kw(ident: &'a str) -> Self { match ident { "as" => Token::KwAs, "async" => Token::KwAsync, "dyn" => Token::KwDyn, "mod" => Token::KwMod, "type" => Token::KwType, "trait" => Token::KwTrait, "crate" => Token::KwCrate, "fn" => Token::KwFn, "mut" => Token::KwMut, "const" => Token::KwConst, "use" => Token::KwUse, "for" => Token::KwFor, "extern" => Token::KwExtern, "impl" => Token::KwImpl, "import" => Token::KwImport, "merge" => Token::KwMerge, "if" => Token::KwIf, "else" => Token::KwElse, "match" => Token::KwMatch, x => Token::Ident(x), } } } impl Display for Token<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Token::Arrow => write!(f, "->"), Token::ArrowArm => write!(f, "=>"), Token::AngleOpen => write!(f, "<"), Token::AngleClose => write!(f, ">"), Token::BracketOpen => write!(f, "["), Token::BracketClose => write!(f, "]"), Token::ParenOpen => write!(f, "("), Token::ParenClose => write!(f, ")"), Token::BraceOpen => write!(f, "{{"), Token::BraceClose => write!(f, "}}"), Token::Colon => write!(f, ":"), Token::ColonColon => write!(f, "::"), Token::And => write!(f, "&"), Token::Star => write!(f, "*"), Token::Sharp => write!(f, "#"), Token::Plus => write!(f, "+"), Token::Eq => write!(f, "="), Token::Question => write!(f, "?"), Token::Comma => write!(f, ","), Token::Semicolon => write!(f, ";"), Token::Pipe => write!(f, "|"), Token::Underscore => write!(f, "_"), Token::Dot => write!(f, "."), Token::Bang => write!(f, "!"), Token::KwAs => write!(f, "as"), Token::KwAsync => write!(f, "async"), Token::KwDyn => write!(f, "dyn"), Token::KwUse => write!(f, "use"), Token::KwFor => write!(f, "for"), Token::KwMod => write!(f, "mod"), Token::KwCrate => write!(f, "crate"), Token::KwType => write!(f, "type"), Token::KwTrait => write!(f, "trait"), Token::KwFn => write!(f, "fn"), Token::KwMut => write!(f, "mut"), Token::KwConst => write!(f, "const"), Token::KwExtern => write!(f, "extern"), Token::KwImpl => write!(f, "impl"), Token::KwImport => write!(f, "import"), Token::KwMerge => write!(f, "merge"), Token::KwIf => write!(f, "if"), Token::KwElse => write!(f, "else"), Token::KwMatch => write!(f, "match"), Token::Ident(i) => write!(f, "{i}"), Token::Number(n) => write!(f, "{n}"), Token::Str(s) => write!(f, r#""{s}""#), } } } fn lexer<'src>() -> impl Parser<'src, &'src str, Vec<(Token<'src>, Span)>, extra::Err>> { let token = choice(( choice([ just("->").to(Token::Arrow), just("=>").to(Token::ArrowArm), just("<").to(Token::AngleOpen), just(">").to(Token::AngleClose), just("[").to(Token::BracketOpen), just("]").to(Token::BracketClose), just("(").to(Token::ParenOpen), just(")").to(Token::ParenClose), just("{").to(Token::BraceOpen), just("}").to(Token::BraceClose), just("::").to(Token::ColonColon), just(":").to(Token::Colon), just("&").to(Token::And), just("*").to(Token::Star), just("#").to(Token::Sharp), just("+").to(Token::Plus), just("=").to(Token::Eq), just("?").to(Token::Question), just(",").to(Token::Comma), just(";").to(Token::Semicolon), just("|").to(Token::Pipe), just("_").to(Token::Underscore), just(".").to(Token::Dot), just("!").to(Token::Bang), ]), text::ident().map(Token::ident_or_kw), text::int(10).map(|x: &str| Token::Number(x.parse().unwrap())), just('"') .ignore_then(none_of('"').repeated().to_slice().map(Token::Str)) .then_ignore(just('"')), )); let comment = just("//") .then(any().and_is(just('\n').not()).repeated()) .padded(); token .map_with(|tok, extra| (tok, extra.span())) .padded_by(comment.repeated()) .padded() .repeated() .collect() } fn alias<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just(Token::KwUse) .ignore_then(path()) .then_ignore(just(Token::KwAs)) .then(select! { Token::Ident(c) => c, }) .then_ignore(just(Token::Semicolon)) .map_with(|(path, name), extra| { ParsedItem::Alias(ParsedAlias { name, path, span: extra.span(), }) }) .boxed() } fn file_parser<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedZngFile<'a>, ZngParserExtra<'a>> + Clone { item().repeated().collect::>().map(ParsedZngFile) } fn rust_type<'a>() -> Boxed<'a, 'a, ParserInput<'a>, ParsedRustType<'a>, ZngParserExtra<'a>> { let as_scalar = |s: &str, head: char| -> Option { let s = s.strip_prefix(head)?; s.parse().ok() }; let scalar = select! { Token::Ident("bool") => PrimitiveRustType::Bool, Token::Ident("str") => PrimitiveRustType::Str, Token::Ident("char") => PrimitiveRustType::Char, Token::Ident("ZngurCppOpaqueOwnedObject") => PrimitiveRustType::ZngurCppOpaqueOwnedObject, Token::Ident("usize") => PrimitiveRustType::Usize, Token::Ident(c) if as_scalar(c, 'u').is_some() => PrimitiveRustType::Uint(as_scalar(c, 'u').unwrap()), Token::Ident(c) if as_scalar(c, 'i').is_some() => PrimitiveRustType::Int(as_scalar(c, 'i').unwrap()), Token::Ident(c) if as_scalar(c, 'f').is_some() => PrimitiveRustType::Float(as_scalar(c, 'f').unwrap()), }.map(ParsedRustType::Primitive); recursive(|parser| { let parser = parser.boxed(); let pg = rust_path_and_generics(parser.clone()); let adt = pg.clone().map(ParsedRustType::Adt); let dyn_trait = just(Token::KwDyn) .or(just(Token::KwImpl)) .then(rust_trait(parser.clone())) .then( just(Token::Plus) .ignore_then(select! { Token::Ident(c) => c, }) .repeated() .collect::>(), ) .map(|((token, first), rest)| match token { Token::KwDyn => ParsedRustType::Dyn(first, rest), Token::KwImpl => ParsedRustType::Impl(first, rest), _ => unreachable!(), }); let boxed = just(Token::Ident("Box")) .then(rust_generics(parser.clone())) .map(|(_, x)| { assert_eq!(x.len(), 1); ParsedRustType::Boxed(Box::new(x.into_iter().next().unwrap().right().unwrap())) }); let unit = just(Token::ParenOpen) .then(just(Token::ParenClose)) .map(|_| ParsedRustType::Tuple(vec![])); let tuple = parser .clone() .separated_by(just(Token::Comma)) .allow_trailing() .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)) .map(|xs| ParsedRustType::Tuple(xs)); let slice = parser .clone() .map(|x| ParsedRustType::Slice(Box::new(x))) .delimited_by(just(Token::BracketOpen), just(Token::BracketClose)); let reference = just(Token::And) .ignore_then( just(Token::KwMut) .to(Mutability::Mut) .or(empty().to(Mutability::Not)), ) .then(parser.clone()) .map(|(m, x)| ParsedRustType::Ref(m, Box::new(x))); let raw_ptr = just(Token::Star) .ignore_then( just(Token::KwMut) .to(Mutability::Mut) .or(just(Token::KwConst).to(Mutability::Not)), ) .then(parser) .map(|(m, x)| ParsedRustType::Raw(m, Box::new(x))); choice(( scalar, boxed, unit, tuple, slice, adt, reference, raw_ptr, dyn_trait, )) }) .boxed() } fn rust_generics<'a>( rust_type: Boxed<'a, 'a, ParserInput<'a>, ParsedRustType<'a>, ZngParserExtra<'a>>, ) -> impl Parser< 'a, ParserInput<'a>, Vec), ParsedRustType<'a>>>, ZngParserExtra<'a>, > + Clone { let named_generic = select! { Token::Ident(c) => c, } .then_ignore(just(Token::Eq)) .then(rust_type.clone()) .map(Either::Left); just(Token::ColonColon).repeated().at_most(1).ignore_then( named_generic .or(rust_type.clone().map(Either::Right)) .separated_by(just(Token::Comma)) .allow_trailing() .collect::>() .delimited_by(just(Token::AngleOpen), just(Token::AngleClose)), ) } fn rust_path_and_generics<'a>( rust_type: Boxed<'a, 'a, ParserInput<'a>, ParsedRustType<'a>, ZngParserExtra<'a>>, ) -> impl Parser<'a, ParserInput<'a>, ParsedRustPathAndGenerics<'a>, ZngParserExtra<'a>> + Clone { let generics = rust_generics(rust_type.clone()); path() .then(generics.clone().repeated().at_most(1).collect::>()) .map(|x| { let generics = x.1.into_iter().next().unwrap_or_default(); let (named_generics, generics) = generics.into_iter().partition_map(|x| x); ParsedRustPathAndGenerics { path: x.0, generics, named_generics, } }) } fn fn_args<'a>( rust_type: Boxed<'a, 'a, ParserInput<'a>, ParsedRustType<'a>, ZngParserExtra<'a>>, ) -> impl Parser<'a, ParserInput<'a>, (Vec>, ParsedRustType<'a>), ZngParserExtra<'a>> + Clone { rust_type .clone() .separated_by(just(Token::Comma)) .allow_trailing() .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)) .then( just(Token::Arrow) .ignore_then(rust_type) .or(empty().to(ParsedRustType::Tuple(vec![]))), ) .boxed() } fn spanned<'a, T>( parser: impl Parser<'a, ParserInput<'a>, T, ZngParserExtra<'a>> + Clone, ) -> impl Parser<'a, ParserInput<'a>, Spanned, ZngParserExtra<'a>> + Clone { parser.map_with(|inner, extra| Spanned { inner, span: extra.span(), }) } fn rust_trait<'a>( rust_type: Boxed<'a, 'a, ParserInput<'a>, ParsedRustType<'a>, ZngParserExtra<'a>>, ) -> impl Parser<'a, ParserInput<'a>, ParsedRustTrait<'a>, ZngParserExtra<'a>> + Clone { let fn_trait = select! { Token::Ident(c) => c, } .then(fn_args(rust_type.clone())) .map(|x| ParsedRustTrait::Fn { name: x.0, inputs: x.1.0, output: Box::new(x.1.1), }); let rust_trait = fn_trait.or(rust_path_and_generics(rust_type).map(ParsedRustTrait::Normal)); rust_trait } fn method<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedMethod<'a>, ZngParserExtra<'a>> + Clone { spanned(just(Token::KwAsync)) .or_not() .then_ignore(just(Token::KwFn)) .then(select! { Token::Ident(c) => c, }) .then( rust_type() .separated_by(just(Token::Comma)) .collect::>() .delimited_by(just(Token::AngleOpen), just(Token::AngleClose)) .or(empty().to(vec![])), ) .then(fn_args(rust_type())) .map(|(((opt_async, name), generics), args)| { let is_self = |c: &ParsedRustType<'_>| { if let ParsedRustType::Adt(c) = c { c.path.start == ParsedPathStart::Relative && &c.path.segments == &["self"] && c.generics.is_empty() } else { false } }; let (inputs, receiver) = match args.0.get(0) { Some(x) if is_self(&x) => (args.0[1..].to_vec(), ZngurMethodReceiver::Move), Some(ParsedRustType::Ref(m, x)) if is_self(&x) => { (args.0[1..].to_vec(), ZngurMethodReceiver::Ref(*m)) } _ => (args.0, ZngurMethodReceiver::Static), }; let mut output = args.1; if let Some(async_kw) = opt_async { output = ParsedRustType::Impl( ParsedRustTrait::Normal(ParsedRustPathAndGenerics { path: ParsedPath { start: ParsedPathStart::Absolute, segments: vec!["std", "future", "Future"], span: async_kw.span, }, generics: vec![], named_generics: vec![("Output", output)], }), vec![], ) } ParsedMethod { name, receiver, generics, inputs, output, } }) } fn inner_type_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedTypeItem<'a>, ZngParserExtra<'a>> + Clone { let property_item = (spanned(select! { Token::Ident(c) => c, })) .then_ignore(just(Token::Eq)) .then(select! { Token::Number(c) => c, }); let layout = just([Token::Sharp, Token::Ident("layout")]) .ignore_then( property_item .clone() .separated_by(just(Token::Comma)) .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map(ParsedLayoutPolicy::StackAllocated) .or(just([Token::Sharp, Token::Ident("layout_conservative")]) .ignore_then( property_item .separated_by(just(Token::Comma)) .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map(ParsedLayoutPolicy::Conservative)) .or(just([Token::Sharp, Token::Ident("only_by_ref")]).to(ParsedLayoutPolicy::OnlyByRef)) .or(just([Token::Sharp, Token::Ident("heap_allocated")]) .to(ParsedLayoutPolicy::HeapAllocated)) .map_with(|x, extra| ParsedTypeItem::Layout(extra.span(), x)) .boxed(); let trait_item = select! { Token::Ident("Debug") => ZngurWellknownTrait::Debug, Token::Ident("Copy") => ZngurWellknownTrait::Copy, } .or(just(Token::Question) .then(just(Token::Ident("Sized"))) .to(ZngurWellknownTrait::Unsized)); let traits = just(Token::Ident("wellknown_traits")) .ignore_then( spanned(trait_item) .separated_by(just(Token::Comma)) .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map(ParsedTypeItem::Traits) .boxed(); let constructor_args = rust_type() .separated_by(just(Token::Comma)) .collect::>() .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)) .map(ParsedConstructorArgs::Tuple) .or((select! { Token::Ident(c) => c, }) .boxed() .then_ignore(just(Token::Colon)) .then(rust_type()) .separated_by(just(Token::Comma)) .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)) .map(ParsedConstructorArgs::Named)) .or(empty().to(ParsedConstructorArgs::Unit)) .boxed(); let constructor = just(Token::Ident("constructor")).ignore_then( (select! { Token::Ident(c) => Some(c), }) .or(empty().to(None)) .then(constructor_args) .map(|(name, args)| ParsedTypeItem::Constructor { name, args }), ); let field = just(Token::Ident("field")).ignore_then( (select! { Token::Ident(c) => c.to_owned(), Token::Number(c) => c.to_string(), }) .then( just(Token::Ident("offset")) .then(just(Token::Eq)) .ignore_then(select! { Token::Number(c) => Some(c), Token::Ident("auto") => None, }) .then( just(Token::Comma) .then(just(Token::KwType)) .then(just(Token::Eq)) .ignore_then(rust_type()), ) .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)), ) .map(|(name, (offset, ty))| ParsedTypeItem::Field { name, ty, offset }), ); let cpp_value = just(Token::Sharp) .then(just(Token::Ident("cpp_value"))) .ignore_then(select! { Token::Str(c) => c, }) .then(select! { Token::Str(c) => c, }) .map(|x| ParsedTypeItem::CppValue { field: x.0, cpp_type: x.1, }); let cpp_ref = just(Token::Sharp) .then(just(Token::Ident("cpp_ref"))) .ignore_then(select! { Token::Str(c) => c, }) .map(|x| ParsedTypeItem::CppRef { cpp_type: x }); recursive(|item| { let inner_item = choice(( layout, traits, constructor, field, cpp_value, cpp_ref, method() .then( just(Token::KwUse) .ignore_then(path()) .map(Some) .or(empty().to(None)), ) .then( just(Token::Ident("deref")) .ignore_then(rust_type()) .map(Some) .or(empty().to(None)), ) .map(|((data, use_path), deref)| ParsedTypeItem::Method { deref, use_path, data, }), )); let match_stmt = conditional_item::<_, CfgConditional<'a>, NItems>(item).map(ParsedTypeItem::MatchOnCfg); choice((match_stmt, inner_item.then_ignore(just(Token::Semicolon)))) }) } fn type_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just(Token::KwType) .ignore_then(spanned(rust_type())) .then( spanned(inner_type_item()) .repeated() .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)), ) .map(|(ty, items)| ParsedItem::Type { ty, items }) .boxed() } fn trait_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just(Token::KwTrait) .ignore_then(spanned(rust_trait(rust_type()))) .then( method() .then_ignore(just(Token::Semicolon)) .repeated() .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)), ) .map(|(tr, methods)| ParsedItem::Trait { tr, methods }) .boxed() } fn fn_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { spanned(method()) .then_ignore(just(Token::Semicolon)) .map(ParsedItem::Fn) } fn additional_include_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just(Token::Sharp) .ignore_then(choice(( just(Token::Ident("cpp_additional_includes")).ignore_then(select! { Token::Str(c) => ParsedItem::CppAdditionalInclude(c), }), just(Token::Ident("convert_panic_to_exception")) .map_with(|_, extra| ParsedItem::ConvertPanicToException(extra.span())), ))) .boxed() } fn extern_cpp_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { let function = spanned(method()) .then_ignore(just(Token::Semicolon)) .map(ParsedExternCppItem::Function); let impl_block = just(Token::KwImpl) .ignore_then( rust_trait(rust_type()) .then_ignore(just(Token::KwFor)) .map(Some) .or(empty().to(None)) .then(spanned(rust_type())), ) .then( method() .then_ignore(just(Token::Semicolon)) .repeated() .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)), ) .map(|((tr, ty), methods)| ParsedExternCppItem::Impl { tr, ty, methods }); just(Token::KwExtern) .then(just(Token::Str("C++"))) .ignore_then( function .or(impl_block) .repeated() .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)) .boxed(), ) .map(ParsedItem::ExternCpp) .boxed() } fn unstable_feature<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just([Token::Sharp, Token::Ident("unstable")]).ignore_then( select! { Token::Ident(feat) => feat } .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)) .try_map_with(|feat, e| match feat { "cfg_match" => { let ctx: &mut extra::SimpleState = e.state(); ctx.unstable_features.cfg_match = true; Ok(ParsedItem::UnstableFeature("cfg_match")) } "cfg_if" => { let ctx: &mut extra::SimpleState = e.state(); ctx.unstable_features.cfg_if = true; Ok(ParsedItem::UnstableFeature("cfg_if")) } _ => Err(Rich::custom( e.span(), format!("unknown unstable feature '{feat}'"), )), }), ) } fn item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { recursive(|item| { choice(( unstable_feature(), just(Token::KwMod) .ignore_then(path()) .then( item.clone() .repeated() .collect::>() .delimited_by(just(Token::BraceOpen), just(Token::BraceClose)), ) .map(|(path, items)| ParsedItem::Mod { path, items }), type_item(), trait_item(), extern_cpp_item(), fn_item(), additional_include_item(), import_item(), module_import_item(), alias(), conditional_item::<_, CfgConditional<'a>, NItems>(item).map(ParsedItem::MatchOnCfg), )) }) .boxed() } fn import_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just(Token::KwMerge) .ignore_then(select! { Token::Str(path) => path, }) .then_ignore(just(Token::Semicolon)) .map_with(|path, extra| { ParsedItem::Import(ParsedImportPath { path: std::path::PathBuf::from(path), span: extra.span(), }) }) .boxed() } fn module_import_item<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedItem<'a>, ZngParserExtra<'a>> + Clone { just(Token::KwImport) .ignore_then(select! { Token::Str(path) => path }) .then_ignore(just(Token::Semicolon)) .map_with(|path, extra| ParsedItem::ModuleImport { path: std::path::PathBuf::from(path), span: extra.span(), }) .boxed() } fn path<'a>() -> impl Parser<'a, ParserInput<'a>, ParsedPath<'a>, ZngParserExtra<'a>> + Clone { let start = choice(( just(Token::ColonColon).to(ParsedPathStart::Absolute), just(Token::KwCrate) .then(just(Token::ColonColon)) .to(ParsedPathStart::Crate), empty().to(ParsedPathStart::Relative), )); start .then( (select! { Token::Ident(c) => c, }) .separated_by(just(Token::ColonColon)) .at_least(1) .collect::>(), ) .or(just(Token::KwCrate).to((ParsedPathStart::Crate, vec![]))) .map_with(|(start, segments), extra| ParsedPath { start, segments, span: extra.span(), }) .boxed() } impl<'a> conditional::BodyItem for crate::ParsedTypeItem<'a> { type Processed = Self; fn process(self, _ctx: &mut ParseContext) -> Self::Processed { self } } impl<'a> conditional::BodyItem for crate::ParsedItem<'a> { type Processed = ProcessedItemOrAlias<'a>; fn process(self, ctx: &mut ParseContext) -> Self::Processed { crate::process_parsed_item(self, ctx) } } ================================================ FILE: zngur-parser/src/tests.rs ================================================ use std::panic::catch_unwind; use expect_test::{Expect, expect}; use zngur_def::{LayoutPolicy, RustPathAndGenerics, RustType, ZngurSpec}; use crate::{ ImportResolver, ParsedZngFile, cfg::{InMemoryRustCfgProvider, NullCfg, RustCfgProvider}, }; fn check_success(zng: &str) { let _ = ParsedZngFile::parse_str(zng, NullCfg); } pub struct ErrorText(pub String); fn check_fail(zng: &str, error: Expect) { let r = catch_unwind(|| { let _ = ParsedZngFile::parse_str(zng, NullCfg); }); match r { Ok(_) => panic!("Parsing succeeded but we expected fail"), Err(e) => match e.downcast::() { Ok(t) => error.assert_eq(&t.0), Err(e) => std::panic::resume_unwind(e), }, } } fn check_fail_with_cfg( zng: &str, cfg: impl RustCfgProvider + std::panic::UnwindSafe + 'static, error: Expect, ) { let r = catch_unwind(|| { let _ = ParsedZngFile::parse_str(zng, cfg); }); match r { Ok(_) => panic!("Parsing succeeded but we expected fail"), Err(e) => match e.downcast::() { Ok(t) => error.assert_eq(&t.0), Err(e) => std::panic::resume_unwind(e), }, } } fn check_import_fail(zng: &str, error: Expect, resolver: &MockFilesystem) { let r = catch_unwind(|| { let _ = ParsedZngFile::parse_str_with_resolver(zng, NullCfg, resolver); }); match r { Ok(_) => panic!("Parsing succeeded but we expected fail"), Err(e) => match e.downcast::() { Ok(t) => error.assert_eq(&t.0), Err(e) => std::panic::resume_unwind(e), }, } } // useful for debugging a test that should succeeded on parse fn catch_parse_fail( zng: &str, cfg: impl RustCfgProvider + std::panic::UnwindSafe + 'static, ) -> crate::ParseResult { let r = catch_unwind(move || ParsedZngFile::parse_str(zng, cfg)); match r { Ok(r) => r, Err(e) => match e.downcast::() { Ok(t) => { eprintln!("{}", &t.0); crate::ParseResult { spec: ZngurSpec::default(), processed_files: Vec::new(), } } Err(e) => std::panic::resume_unwind(e), }, } } #[test] fn parse_unit() { check_fail( r#" type () { #layout(size = 0, align = 1); wellknown_traits(Copy); } "#, expect![[r#" Error: Unit type is declared implicitly. Remove this entirely. ╭─[test.zng:2:6] │ 2 │ type () { │ ─┬ │ ╰── Unit type is declared implicitly. Remove this entirely. ───╯ "#]], ); } #[test] fn parse_tuple() { check_success( r#" type (i8, u8) { #layout(size = 0, align = 1); } "#, ); } #[test] fn typo_in_wellknown_trait() { check_fail( r#" type () { #layout(size = 0, align = 1); welcome_traits(Copy); } "#, expect![[r#" Error: found 'welcome_traits' expected '#', 'wellknown_traits', 'constructor', 'field', 'async', 'fn', or '}' ╭─[test.zng:4:5] │ 4 │ welcome_traits(Copy); │ ───────┬────── │ ╰──────── found 'welcome_traits' expected '#', 'wellknown_traits', 'constructor', 'field', 'async', 'fn', or '}' ───╯ "#]], ); } #[test] fn multiple_layout_policies() { check_fail( r#" type ::std::string::String { #layout(size = 24, align = 8); #heap_allocated; } "#, expect![[r#" Error: Duplicate layout policy found ╭─[test.zng:4:5] │ 4 │ #heap_allocated; │ ───────┬─────── │ ╰───────── Duplicate layout policy found ───╯ "#]], ); } #[test] fn cpp_ref_should_not_need_layout_info() { check_fail( r#" type crate::Way { #layout(size = 1, align = 2); #cpp_ref "::osmium::Way"; } "#, expect![[r#" Error: Duplicate layout policy found ╭─[test.zng:3:5] │ 3 │ #layout(size = 1, align = 2); │ ──────────────┬───────────── │ ╰─────────────── Duplicate layout policy found ───╯ "#]], ); check_success( r#" type crate::Way { #cpp_ref "::osmium::Way"; } "#, ); } macro_rules! assert_ty_path { ($path_expected:expr, $ty:expr) => {{ let RustType::Adt(RustPathAndGenerics { path: p, .. }) = $ty else { panic!("type `{:?}` is not a path", $ty); }; assert_eq!(p.as_slice(), $path_expected); }}; } #[test] fn alias_expands_correctly() { let parsed = ParsedZngFile::parse_str( r#" use ::std::string::String as MyString; type MyString { #layout(size = 24, align = 8); } "#, NullCfg, ); let ty = parsed.spec.types.first().expect("no type parsed"); let RustType::Adt(RustPathAndGenerics { path: p, .. }) = &ty.ty else { panic!("no match?"); }; assert_eq!(p.as_slice(), ["std", "string", "String"]); } #[test] fn alias_expands_nearest_scope_first() { let parsed = ParsedZngFile::parse_str( r#" use ::std::string::String as MyString; mod crate { use MyLocalString as MyString; type MyString { #layout(size = 24, align = 8); } } "#, NullCfg, ); let ty = parsed.spec.types.first().expect("no type parsed"); let RustType::Adt(RustPathAndGenerics { path: p, .. }) = &ty.ty else { panic!("no match?"); }; assert_eq!(p.as_slice(), ["crate", "MyLocalString"]); } struct MockFilesystem { files: std::collections::HashMap, } impl MockFilesystem { fn new( files: impl IntoIterator, impl Into)>, ) -> Self { Self { files: files .into_iter() .map(|(k, v)| (k.into(), v.into())) .collect(), } } } impl ImportResolver for MockFilesystem { fn resolve_import( &self, cwd: &std::path::Path, relpath: &std::path::Path, ) -> Result { let path = cwd.join(relpath); self.files .get(&path) .cloned() .ok_or_else(|| format!("File not found: {}", path.display())) } } #[test] fn import_parser_test() { let resolver = MockFilesystem::new(vec![( "./relative/path.zng", "type Imported { #layout(size = 1, align = 1); }", )]); let parsed = ParsedZngFile::parse_str_with_resolver( r#" merge "./relative/path.zng"; type Example { #layout(size = 1, align = 1); } "#, NullCfg, &resolver, ); assert_eq!(parsed.spec.types.len(), 2); } #[test] fn module_import_prohibited() { let resolver = MockFilesystem::new(vec![] as Vec<(&str, &str)>); check_import_fail( r#" merge "foo/bar.zng"; "#, expect![[r#" Error: Module import is not supported. Use a relative path instead. ╭─[test.zng:2:5] │ 2 │ merge "foo/bar.zng"; │ ──────────┬───────── │ ╰─────────── Module import is not supported. Use a relative path instead. ───╯ "#]], &resolver, ); } #[test] fn import_has_conflict() { // Test that an import which introduces a conflict produces a reasonable error message. let resolver = MockFilesystem::new(vec![( "./a.zng", r#" type A { #layout(size = 1, align = 1); } "#, )]); check_import_fail( r#" merge "./a.zng"; type A { #layout(size = 2, align = 2); } "#, expect![[r#" Error: Duplicate layout policy found ╭─[a.zng:2:12] │ 2 │ type A { │ ┬ │ ╰── Duplicate layout policy found ───╯ "#]], &resolver, ); } #[test] fn import_not_found() { let resolver = MockFilesystem::new(vec![] as Vec<(&str, &str)>); check_import_fail( r#" merge "./a.zng"; "#, expect![[r#" Error: Import path not found: ./a.zng "#]], &resolver, ); } #[test] fn import_has_mismatched_method_signature() { let resolver = MockFilesystem::new(vec![( "./a.zng", "type A { #layout(size = 1, align = 1); fn foo(i32) -> i32; }", )]); check_import_fail( r#" merge "./a.zng"; type A { #layout(size = 1, align = 1); fn foo(i64) -> i64; } "#, expect![[r#" Error: Method mismatch ╭─[a.zng:1:6] │ 1 │ type A { #layout(size = 1, align = 1); fn foo(i32) -> i32; } │ ┬ │ ╰── Method mismatch ───╯ "#]], &resolver, ); } #[test] fn import_has_mismatched_field() { let resolver = MockFilesystem::new(vec![( "./a.zng", "type A { #layout(size = 1, align = 1); field x (offset = 0, type = i32); }", )]); check_import_fail( r#" merge "./a.zng"; type A { #layout(size = 1, align = 1); field x (offset = 0, type = i64); } "#, expect![[r#" Error: Field mismatch ╭─[a.zng:1:6] │ 1 │ type A { │ ┬ │ ╰── Field mismatch ───╯ "#]], &resolver, ); } #[test] fn convert_panic_to_exception_in_imported_file_fails() { let resolver = MockFilesystem::new(vec![( "./imported.zng", r#" #convert_panic_to_exception type A { #layout(size = 1, align = 1); } "#, )]); check_import_fail( r#" merge "./imported.zng"; type B { #layout(size = 1, align = 1); } "#, expect![[r#" Error: Using `#convert_panic_to_exception` in imported zngur files is not supported. This directive can only be used in the main zngur file. ╭─[imported.zng:2:10] │ 2 │ #convert_panic_to_exception │ ─────────────┬──────────── │ ╰────────────── Using `#convert_panic_to_exception` in imported zngur files is not supported. This directive can only be used in the main zngur file. ───╯ "#]], &resolver, ); } #[test] fn convert_panic_to_exception_in_main_file_succeeds() { check_success( r#" #convert_panic_to_exception type A { #layout(size = 1, align = 1); } "#, ); } // Tests for processed_files tracking (depfile support) #[test] fn processed_files_single_file() { let parsed = ParsedZngFile::parse_str( r#" type A { #layout(size = 1, align = 1); } "#, NullCfg, ); // Should have exactly one file (test.zng) assert_eq!(parsed.processed_files.len(), 1); assert_eq!( parsed.processed_files[0] .file_name() .unwrap() .to_str() .unwrap(), "test.zng" ); } #[test] fn processed_files_with_import() { let resolver = MockFilesystem::new(vec![( "./imported.zng", "type Imported { #layout(size = 1, align = 1); }", )]); let parsed = ParsedZngFile::parse_str_with_resolver( r#" merge "./imported.zng"; type Main { #layout(size = 1, align = 1); } "#, NullCfg, &resolver, ); // Should have two files: main (test.zng) + imported assert_eq!(parsed.processed_files.len(), 2); let file_names: Vec<_> = parsed .processed_files .iter() .map(|p| p.file_name().unwrap().to_str().unwrap()) .collect(); assert!(file_names.contains(&"test.zng")); assert!(file_names.contains(&"imported.zng")); } #[test] fn processed_files_with_nested_imports() { let resolver = MockFilesystem::new(vec![ ( "./a.zng", r#"merge "./b.zng"; type A { #layout(size = 1, align = 1); }"#, ), ( "./b.zng", r#"merge "./c.zng"; type B { #layout(size = 1, align = 1); }"#, ), ("./c.zng", "type C { #layout(size = 1, align = 1); }"), ]); let parsed = ParsedZngFile::parse_str_with_resolver( r#" merge "./a.zng"; type Main { #layout(size = 1, align = 1); } "#, NullCfg, &resolver, ); // Should have four files: main + a + b + c assert_eq!(parsed.processed_files.len(), 4); let file_names: Vec<_> = parsed .processed_files .iter() .map(|p| p.file_name().unwrap().to_str().unwrap()) .collect(); assert!(file_names.contains(&"test.zng")); assert!(file_names.contains(&"a.zng")); assert!(file_names.contains(&"b.zng")); assert!(file_names.contains(&"c.zng")); } fn assert_layout(wanted_size: usize, wanted_align: usize, layout: &LayoutPolicy) { if !matches!(layout, LayoutPolicy::StackAllocated { size, align } if *size == wanted_size && *align == wanted_align) { panic!( "no match: StackAllocated {{ size: {wanted_size}, align: {wanted_align} }} != {:?} ", layout ); }; } static EMPTY_CFG: [(&str, &[&str]); 0] = []; #[test] fn test_if_conditional_type_item() { let source = r#" #unstable(cfg_if) type ::std::string::String { #if cfg!(target_pointer_width = "64") { #layout(size = 24, align = 8); } #else if cfg!(target_pointer_width = "32") { #layout(size = 12, align = 4); } #else { // silly size for testing #layout(size = 27, align = 9); } } "#; let parsed = catch_parse_fail( source, InMemoryRustCfgProvider::default().with_values([("target_pointer_width", &["64"])]), ); let ty = parsed.spec.types.first().expect("no type parsed"); assert_layout(24, 8, &ty.layout); let parsed = catch_parse_fail( source, InMemoryRustCfgProvider::default().with_values([("target_pointer_width", &["32"])]), ); let ty = parsed.spec.types.first().expect("no type parsed"); assert_layout(12, 4, &ty.layout); let parsed = catch_parse_fail(source, NullCfg); let ty = parsed.spec.types.first().expect("no type parsed"); assert_layout(27, 9, &ty.layout); } #[test] fn test_match_conditional_type_item() { let source = r#" #unstable(cfg_match) type ::std::string::String { #match cfg!(target_pointer_width) { // single item arm "64" => #layout(size = 24, align = 8); // match usize numbers 32 => { #layout(size = 12, align = 4); }, _ => { // silly size for testing #layout(size = 27, align = 9); } } } "#; let parsed = catch_parse_fail( source, InMemoryRustCfgProvider::default().with_values([("target_pointer_width", &["64"])]), ); let ty = parsed.spec.types.first().expect("no type parsed"); assert_layout(24, 8, &ty.layout); let parsed = catch_parse_fail( source, InMemoryRustCfgProvider::default().with_values([("target_pointer_width", &["32"])]), ); let ty = parsed.spec.types.first().expect("no type parsed"); assert_layout(12, 4, &ty.layout); let parsed = catch_parse_fail(source, NullCfg); let ty = parsed.spec.types.first().expect("no type parsed"); assert_layout(27, 9, &ty.layout); } macro_rules! test_paths_with_cfg { ($src:expr, $features_path_pairs:expr) => { for (cfg, path) in $features_path_pairs.iter().map(|(cfg, path)| { ( cfg.into_iter().copied().collect::>(), path.iter().map(|s| s.to_string()).collect::>(), ) }) { let parsed = catch_parse_fail($src, InMemoryRustCfgProvider::default().with_values(cfg)); let ty = parsed.spec.types.first().expect("no type parsed"); assert_ty_path!(path, &ty.ty); } }; } type CfgPathPairs<'a> = &'a [(&'a [(&'a str, &'a [&'a str])], &'a [&'a str])]; #[test] fn conditional_if_spec_item() { let source = r#" #unstable(cfg_if) #if cfg!(feature = "foo") { type crate::Foo { #layout(size = 1, align = 1); } } #else { type crate::Bar { #layout(size = 1, align = 1); } } "#; let pairs: CfgPathPairs = &[ (&[("feature", &["foo"])], &["crate", "Foo"]), (&EMPTY_CFG, &["crate", "Bar"]), ]; test_paths_with_cfg!(source, pairs); } #[test] fn conditional_match_spec_item() { let source = r#" #unstable(cfg_match) #match cfg!(feature) { "foo" => type crate::Foo { #layout(size = 1, align = 1); } _ => { type crate::Bar { #layout(size = 1, align = 1); } } } "#; let pairs: CfgPathPairs = &[ (&[("feature", &["foo"])], &["crate", "Foo"]), (&EMPTY_CFG, &["crate", "Bar"]), ]; test_paths_with_cfg!(source, pairs); } #[test] fn match_pattern_single_cfg() { let source = r#" #unstable(cfg_match) #match cfg!(feature) { "bar" | "zigza" => type crate::BarZigZa { #layout(size = 1, align = 1); } // match two values from a cfg value as a set "foo" & "baz" => type crate::FooBaz { #layout(size = 1, align = 1); } // negative matching (no feature baz) "foo" & !"baz" => type crate::FooNoBaz { #layout(size = 1, align = 1); } _ => { type crate::Zoop { #layout(size = 1, align = 1); } } } "#; let pairs: CfgPathPairs = &[ (&EMPTY_CFG, &["crate", "Zoop"]), (&[("feature", &["foo"])], &["crate", "FooNoBaz"]), (&[("feature", &["bar"])], &["crate", "BarZigZa"]), (&[("feature", &["zigza"])], &["crate", "BarZigZa"]), (&[("feature", &["foo", "baz"])], &["crate", "FooBaz"]), ]; test_paths_with_cfg!(source, pairs); } #[test] fn if_pattern_multi_cfg() { let source = r#" #unstable(cfg_if) // match two cfg keys as a set #if cfg!(feature.foo) && cfg!(target_pointer_width = 32) { type crate::Foo32 { #layout(size = 1, align = 1); } } #else if cfg!(feature.foo = None) && cfg!(target_pointer_width = 64) { type crate::NoFoo64 { #layout(size = 1, align = 1); } } #else if (cfg!(feature.foo = Some) && cfg!(target_pointer_width = 64)) || cfg!(feature.baz) { type crate::Foo64_OrBaz { #layout(size = 1, align = 1); } } #else { type crate::SpecialFoo { #layout(size = 1, align = 1); } } "#; let pairs: CfgPathPairs = &[ ( &[("target_pointer_width", &["32"]), ("feature", &["foo"])], &["crate", "Foo32"], ), ( &[("target_pointer_width", &["64"]), ("feature", &["bar"])], &["crate", "NoFoo64"], ), ( &[("target_pointer_width", &["64"]), ("feature", &["foo"])], &["crate", "Foo64_OrBaz"], ), ( &[("target_pointer_width", &["32"]), ("feature", &["baz"])], &["crate", "Foo64_OrBaz"], ), (&[("feature", &["foo"])], &["crate", "SpecialFoo"]), (&EMPTY_CFG, &["crate", "SpecialFoo"]), ]; test_paths_with_cfg!(source, pairs); } #[test] fn match_pattern_multi_cfg() { let source = r#" #unstable(cfg_match) #match (cfg!(feature.foo), cfg!(target_pointer_width)) { // match two cfg keys as a set (Some, "32") => type crate::Foo32 { #layout(size = 1, align = 1); } (None, 64) => type crate::NoFoo64 { #layout(size = 1, align = 1); } _ => { type crate::SpecialFoo { #layout(size = 1, align = 1); } } } "#; let pairs: CfgPathPairs = &[ ( &[("target_pointer_width", &["32"]), ("feature", &["foo"])], &["crate", "Foo32"], ), ( &[("target_pointer_width", &["64"]), ("feature", &["bar"])], &["crate", "NoFoo64"], ), (&[("feature", &["foo"])], &["crate", "SpecialFoo"]), (&EMPTY_CFG, &["crate", "SpecialFoo"]), ]; test_paths_with_cfg!(source, pairs); } #[test] fn match_pattern_multi_cfg_bad_pattern() { let source = r#" #unstable(cfg_match) #match (cfg!(feature.foo), cfg!(target_pointer_width)) { (Some, "32") => type crate::Foo32 { // would succeed if cfg match attempted #layout(size = 1, align = 1); } "64" => type crate::NoFoo64 { // will fail: cardinality of pattern and tuple don't match #layout(size = 1, align = 1); } _ => { type crate::SpecialFoo { #layout(size = 1, align = 1); } } } "#; check_fail_with_cfg( source, InMemoryRustCfgProvider::default().with_values([("target_pointer_width", &["64"])]), expect![[r#" Error: Can not match single pattern against multiple cfg values. ╭─[test.zng:9:5] │ 9 │ "64" => type crate::NoFoo64 { │ ──┬─ │ ╰─── Can not match single pattern against multiple cfg values. ───╯ "#]], ); } #[test] fn match_pattern_multi_cfg_bad_pattern2() { let source = r#" #unstable(cfg_match) #match (cfg!(feature.foo), cfg!(target_pointer_width), cfg!(target_feature) ) { (Some, "32", "avx" & "avx2") => type crate::Foo32 { // would succeed if cfg match attempted #layout(size = 1, align = 1); } (None, "64") => type crate::NoFoo64 { // will fail: cardinality of pattern and tuple don't match #layout(size = 1, align = 1); } _ => { type crate::SpecialFoo { #layout(size = 1, align = 1); } } } "#; let cfg: [(&str, &[&str]); 2] = [ ("target_pointer_width", &["64"]), ("target_feature", &["avx", "avx2"]), ]; check_fail_with_cfg( source, InMemoryRustCfgProvider::default().with_values(cfg), expect![[r#" Error: Number of patterns and number of scrutinees do not match. ╭─[test.zng:9:5] │ 9 │ (None, "64") => type crate::NoFoo64 { │ ──────┬───── │ ╰─────── Number of patterns and number of scrutinees do not match. ───╯ "#]], ); } #[test] fn cfg_match_unstable() { let source = r#" #match cfg!(feature) { "foo" => type crate::Foo { #layout(size = 1, align = 1); } _ => { type crate::Bar { #layout(size = 1, align = 1); } } } "#; check_fail_with_cfg( source, InMemoryRustCfgProvider::default().with_values([("feature", &["foo"])]), expect![[r#" Error: `#match` statements are unstable. Enable them by using `#unstable(cfg_match)` at the top of the file. ╭─[test.zng:2:1] │ 2 │ ╭─▶ #match cfg!(feature) { ┆ ┆ 11 │ ├─▶ } │ │ │ ╰─────── `#match` statements are unstable. Enable them by using `#unstable(cfg_match)` at the top of the file. ────╯ "#]], ); } #[test] fn module_import_parser_test() { let parsed = crate::ParsedZngFile::parse_str( r#" import "module.zng"; "#, crate::cfg::NullCfg, ); assert_eq!(parsed.spec.imported_modules.len(), 1); assert_eq!( parsed.spec.imported_modules[0].path.to_str().unwrap(), "module.zng" ); }