├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .mailmap ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE_APACHE ├── LICENSE_MIT ├── README.md ├── bump_version.sh ├── cpp ├── Cargo.toml └── src │ └── lib.rs ├── cpp_build ├── Cargo.toml └── src │ ├── lib.rs │ ├── parser.rs │ └── strnom.rs ├── cpp_common ├── Cargo.toml └── src │ └── lib.rs ├── cpp_macros ├── Cargo.toml └── src │ └── lib.rs ├── publish_crates.sh ├── rustfmt.toml └── test ├── Cargo.toml ├── build.rs └── src ├── cpp_class.rs ├── examples.rs ├── header.h ├── inner ├── explicit_path.rs ├── innerinner.rs └── mod.rs ├── inner_sibling.rs ├── inner_sibling ├── child.rs └── child2 │ └── mod.rs ├── invalid_code.rs ├── lib.rs └── nomod └── inner.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | strategy: 16 | matrix: 17 | rust: 18 | - stable 19 | - nightly 20 | - 1.65.0 21 | os: 22 | - ubuntu-latest 23 | - macos-11 24 | - windows-2022 25 | 26 | runs-on: ${{ matrix.os }} 27 | 28 | steps: 29 | - uses: actions/checkout@v3 30 | - uses: dtolnay/rust-toolchain@stable 31 | with: 32 | toolchain: ${{ matrix.rust }} 33 | components: rustfmt, clippy 34 | - name: Build 35 | run: cargo build --verbose 36 | - name: Run tests 37 | run: cargo test --verbose 38 | - name: Run tests (release) 39 | run: cargo test --release --verbose 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Nika Layzell 2 | Olivier Goffart 3 | Olivier Goffart 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.5.10 - 2024-11-20 4 | 5 | - `impl From for cpp_build::Config` 6 | - Fix warning about unexpected cfg in crates using `cpp!` 7 | 8 | ## 0.5.9 - 2023-08-16 9 | 10 | - updated aha-corasick dependency 11 | 12 | ## 0.5.8 - 2023-03-30 13 | 14 | - Fixed clippy warnings 15 | - Added `parallel` feature forwarding to `cc/parallel` 16 | - Ported to `syn 2.0` 17 | 18 | ## 0.5.7 - 2022-04-29 19 | 20 | - Fixed clippy warnings 21 | 22 | ## 0.5.6 - 2020-12-28 23 | 24 | - Fixed module lookup when using mod.rs (#88) 25 | - Increase aho-corasick version to fix #70 26 | - This is the first release that has a Changelog 27 | 28 | 29 | ## History 30 | 31 | `rust-cpp` has had multiple different implementations. The code for these old 32 | implementations is still present in the tree today. 33 | 34 | #### [`rustc_plugin`](https://github.com/mystor/rust-cpp/tree/legacy_rustc_plugin) 35 | 36 | `rust-cpp` started life as a unstable compiler plugin. This code no longer 37 | builds on modern nightly rusts, but it had some features which are still 38 | unavailable on more recent versions of `rust-cpp`, as it was able to take 39 | advantage of the rust compiler's type inference by abusing a lint pass to 40 | collect type information. 41 | 42 | Development on the original version ceased for 2 major reasons: 43 | 44 | 1) The rustc internal libraries changed very often, meaning that constant 45 | maintenance work was required to keep it working on the latest nightly 46 | versions. 47 | 48 | 2) The plugin had no support for stable rust, which is undesirable because the 49 | majority of crates are not built with the latest nightly compiler, and 50 | requiring unstable features is a deal breaker for them. 51 | 52 | These limitations led to the development of the next phase of `rust-cpp`'s 53 | lifetime. 54 | 55 | #### [stable (a.k.a `v0.1`)](https://github.com/mystor/rust-cpp/tree/legacy_v0.1) 56 | 57 | The next phase in `rust-cpp`'s lifetime was when it was rewritten as a 58 | `syntex` plugin. `syntex` is an extraction of the rust compiler's 59 | internal `syntax` library, and has support for performing procedural macro 60 | expansions by rewriting rust source files. 61 | 62 | Performing a full rewrite of the source tree was very unfortunate, as it would 63 | mean that all compiler errors in crates which use the plugin would be reported 64 | in a generated file instead of at the original source location. Instead, this 65 | version of `rust-cpp` used a stable `macro_rules!` macro to perform the rust 66 | code generation, and a build step based on `syntex` to perform the c++ code 67 | generation and compilation. 68 | 69 | Unfortunately this architecture meant that one of the neatest features, 70 | closures, was not available in this version of `rust-cpp`. Implementing 71 | that feature required some form of procedural code generation on the rust 72 | side, which was not possible in rust at that time without performing full text 73 | rewrites of the source code. 74 | 75 | #### Macros 1.1 and syn (a.k.a. `v0.2`) 76 | 77 | This is the current implementation of `rust-cpp`. In `rustc 1.15`, the first 78 | form of procedural macros, custom derive, was stabilized. Alongside this, 79 | @dtolnay implemented [`syn`](https://github.com/dtolnay/syn), which was a small, 80 | fast to compile, crate for parsing rust code. `rust-cpp` uses a fork of `syn` 81 | for its rust code parsing. 82 | 83 | `rust-cpp` now internally uses a custom derive to implement the procedural 84 | components of the rust code generation, which means that closures are available 85 | again! It also builds much more quickly than the previous version as it no 86 | longer depends on `syntex` which could take a long time to build. 87 | 88 | The fork of `syn` ([`cpp_syn`](https://github.com/mystor/cpp_syn)) which 89 | `rust-cpp` uses differs from `syn` in that it keeps track of source location 90 | information for each AST node. This feature has not been landed into `syn` yet 91 | as it is a breaking change, and none of `syn`'s other consumers would make use 92 | of it yet. 93 | 94 | #### `v0.5` 95 | 96 | The syn version was upgraded to `syn 1.0` 97 | Since `syn` did not allow to access the actual source location or text, the `cpp_build` 98 | uses its own rust lexer, forked from the `proc_macro2` crate. 99 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | 'cpp', 4 | 'cpp_build', 5 | 'cpp_common', 6 | 'cpp_macros', 7 | 'test', 8 | ] 9 | -------------------------------------------------------------------------------- /LICENSE_APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Nika Layzell 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Nika Layzell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-cpp - Embed C++ code directly in Rust 2 | 3 | [![Documentation](https://docs.rs/cpp/badge.svg)](https://docs.rs/cpp/) 4 | 5 | ## Overview 6 | 7 | `rust-cpp` is a build tool & macro which enables you to write C++ code inline in 8 | your rust code. 9 | 10 | ```rust 11 | let name = std::ffi::CString::new("World").unwrap(); 12 | let name_ptr = name.as_ptr(); 13 | let r = unsafe { 14 | cpp!([name_ptr as "const char *"] -> u32 as "int32_t" { 15 | std::cout << "Hello, " << name_ptr << std::endl; 16 | return 42; 17 | }) 18 | }; 19 | assert_eq!(r, 42) 20 | ``` 21 | 22 | The crate also help to expose some C++ class to Rust by automatically 23 | implementing trait such as Drop, Clone (if the C++ type can be copied), and others 24 | 25 | ```rust 26 | cpp_class!{ 27 | #[derive(PartialEq)] 28 | unsafe struct MyClass as "std::unique_ptr" 29 | } 30 | ``` 31 | 32 | ## Usage 33 | 34 | For usage information and in-depth documentation, see 35 | the [`cpp` crate module level documentation](https://docs.rs/cpp). 36 | 37 | 38 | ## Differences with the [`cxx`](https://cxx.rs) crate 39 | 40 | This crate allows to write C++ code "inline" within your Rust functions, while with the [`cxx`](https://cxx.rs) crate, you have 41 | to write a bit of boiler plate to have calls to functions declared in a different `.cpp` file. 42 | 43 | Having C++ code inline might be helpful when trying to call to a C++ library and that one may wish to make plenty of call to small snippets. 44 | It can otherwise be fastidious to write and maintain the boiler plate for many small functions in different places. 45 | 46 | These crate can also be used in together. The `cxx` crate offer some useful types such as `CxxString` that can also be used with this crate. 47 | 48 | The `cxx` bridge does more type checking which can avoid some classes of errors. While this crate can only check for equal size and alignment. 49 | -------------------------------------------------------------------------------- /bump_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | version=$1 4 | old_version=`sed -n 's/^version = "\(.\+\)"/\1/p' cpp/Cargo.toml` 5 | 6 | echo "updating $old_version to $version" 7 | 8 | for toml in */Cargo.toml; do 9 | cp $toml $toml.bk 10 | cat $toml.bk \ 11 | | sed -e 's/\(\(^\|cpp_.\+\)version = \"=\?\)'$old_version'/\1'$version'/g' \ 12 | > $toml 13 | rm $toml.bk 14 | done 15 | 16 | -------------------------------------------------------------------------------- /cpp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp" 3 | version = "0.5.10" 4 | authors = ["Nika Layzell ", "Olivier Goffart "] 5 | edition = "2018" 6 | description = "Inline C++ code closures" 7 | readme = "../README.md" 8 | license = "MIT/Apache-2.0" 9 | keywords = ["c", "cxx", "ffi", "compiler"] 10 | categories = ["development-tools::ffi"] 11 | repository = "https://github.com/mystor/rust-cpp" 12 | documentation = "https://docs.rs/cpp" 13 | 14 | [dependencies] 15 | cpp_macros = { version = "=0.5.10", path = "../cpp_macros" } 16 | 17 | [dev-dependencies] 18 | cpp_build = { version = "=0.5.10", path = "../cpp_build" } 19 | -------------------------------------------------------------------------------- /cpp/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_doctest_main)] 2 | //! This crate `cpp` provides macros that allow embedding arbitrary C++ code. 3 | //! 4 | //! # Usage 5 | //! 6 | //! This crate must be used in tandem with the [`cpp_build`](https://docs.rs/cpp_build) crate. A basic Cargo 7 | //! project which uses these projects would have a structure like the following: 8 | //! 9 | //! ```text 10 | //! crate 11 | //! |-- Cargo.toml 12 | //! |-- src 13 | //! |-- main.rs 14 | //! |-- build.rs 15 | //! ``` 16 | //! 17 | //! Where the files look like the following: 18 | //! 19 | //! #### Cargo.toml 20 | //! 21 | //! ```toml 22 | //! [package] 23 | //! build = "build.rs" 24 | //! 25 | //! [dependencies] 26 | //! cpp = "0.5" 27 | //! 28 | //! [build-dependencies] 29 | //! cpp_build = "0.5" 30 | //! ``` 31 | //! 32 | //! #### build.rs 33 | //! 34 | //! ```no_run 35 | //! extern crate cpp_build; 36 | //! fn main() { 37 | //! cpp_build::build("src/main.rs"); 38 | //! } 39 | //! ``` 40 | //! 41 | //! #### main.rs 42 | //! 43 | //! ```ignore 44 | //! # // tested in test/src/examples.rs 45 | //! use cpp::cpp; 46 | //! 47 | //! cpp!{{ 48 | //! #include 49 | //! }} 50 | //! 51 | //! fn main() { 52 | //! let name = std::ffi::CString::new("World").unwrap(); 53 | //! let name_ptr = name.as_ptr(); 54 | //! let r = unsafe { 55 | //! cpp!([name_ptr as "const char *"] -> u32 as "int32_t" { 56 | //! std::cout << "Hello, " << name_ptr << std::endl; 57 | //! return 42; 58 | //! }) 59 | //! }; 60 | //! assert_eq!(r, 42) 61 | //! } 62 | //! ``` 63 | //! 64 | //! # Build script 65 | //! 66 | //! Use the `cpp_build` crates from your `build.rs` script. 67 | //! The same version of `cpp_build` and `cpp` crates should be used. 68 | //! You can simply use the `cpp_build::build` function, or the `cpp_build::Config` 69 | //! struct if you want more option. 70 | //! 71 | //! Behind the scene, it uses the `cc` crate. 72 | //! 73 | //! ## Using external libraries 74 | //! 75 | //! Most likely you will want to link against external libraries. You need to tell cpp_build 76 | //! about the include path and other flags via `cpp_build::Config` and you need to let cargo 77 | //! know about the link. More info in the [cargo docs](https://doc.rust-lang.org/cargo/reference/build-scripts.html). 78 | //! 79 | //! Your `build.rs` could look like this: 80 | //! 81 | //! ```no_run 82 | //! fn main() { 83 | //! let include_path = "/usr/include/myexternallib"; 84 | //! let lib_path = "/usr/lib/myexternallib"; 85 | //! cpp_build::Config::new().include(include_path).build("src/lib.rs"); 86 | //! println!("cargo:rustc-link-search={}", lib_path); 87 | //! println!("cargo:rustc-link-lib=myexternallib"); 88 | //! } 89 | //! ``` 90 | //! 91 | //! (But you probably want to allow to configure the path via environment variables or 92 | //! find them using some external tool such as the `pkg-config` crate, instead of hardcoding 93 | //! them in the source) 94 | //! 95 | //! # Limitations 96 | //! 97 | //! As with all procedure macro crates we also need to parse Rust source files to 98 | //! extract C++ code. That leads to the fact that some of the language features 99 | //! might not be supported in full. One example is the attributes. Only a limited 100 | //! number of attributes is supported, namely: `#[path = "..."]` for `mod` 101 | //! declarations to specify an alternative path to the module file and 102 | //! `#[cfg(feature = "...")]` for `mod` declarations to conditionally include the 103 | //! module into the parsing process. Please note that the latter is only supported 104 | //! in its simplest form: straight-forward `feature = "..."` without any 105 | //! additional conditions, `cfg!` macros are also not supported at the moment. 106 | //! 107 | //! Since the C++ code is included within a rust file, the C++ code must obey both 108 | //! the Rust and the C++ lexing rules. For example, Rust supports nested block comments 109 | //! (`/* ... /* ... */ ... */`) while C++ does not, so nested comments not be used in the 110 | //! `cpp!` macro. Also the Rust lexer will not understand the C++ raw literal, nor all 111 | //! the C++ escape sequences within literal, so only string literals that are both valid 112 | //! in Rust and in C++ should be used. The same applies for group separators in numbers. 113 | //! Be careful to properly use `#if` / `#else` / `#endif`, and not have unbalanced delimiters. 114 | 115 | #![no_std] 116 | 117 | #[macro_use] 118 | #[allow(unused_imports)] 119 | extern crate cpp_macros; 120 | #[doc(hidden)] 121 | pub use cpp_macros::*; 122 | 123 | /// Internal macro which is used to locate the `rust!` invocations in the 124 | /// C++ code embedded in `cpp!` invocation, to translate them into `extern` 125 | /// functions 126 | #[doc(hidden)] 127 | #[macro_export] 128 | macro_rules! __cpp_internal { 129 | (@find_rust_macro [$($a:tt)*] rust!($($rust_body:tt)*) $($rest:tt)*) => { 130 | $crate::__cpp_internal!{ @expand_rust_macro [$($a)*] $($rust_body)* } 131 | $crate::__cpp_internal!{ @find_rust_macro [$($a)*] $($rest)* } 132 | }; 133 | (@find_rust_macro [$($a:tt)*] ( $($in:tt)* ) $($rest:tt)* ) => 134 | { $crate::__cpp_internal!{ @find_rust_macro [$($a)*] $($in)* $($rest)* } }; 135 | (@find_rust_macro [$($a:tt)*] [ $($in:tt)* ] $($rest:tt)* ) => 136 | { $crate::__cpp_internal!{ @find_rust_macro [$($a)*] $($in)* $($rest)* } }; 137 | (@find_rust_macro [$($a:tt)*] { $($in:tt)* } $($rest:tt)* ) => 138 | { $crate::__cpp_internal!{ @find_rust_macro [$($a)*] $($in)* $($rest)* } }; 139 | (@find_rust_macro [$($a:tt)*] $t:tt $($rest:tt)*) => 140 | { $crate::__cpp_internal!{ @find_rust_macro [$($a)*] $($rest)* } }; 141 | (@find_rust_macro [$($a:tt)*]) => {}; 142 | 143 | (@expand_rust_macro [$($a:tt)*] $i:ident [$($an:ident : $at:ty as $ac:tt),*] {$($body:tt)*}) => { 144 | #[allow(non_snake_case)] 145 | #[allow(unused_unsafe)] 146 | #[allow(clippy::forget_copy, clippy::forget_ref)] 147 | #[doc(hidden)] 148 | $($a)* unsafe extern "C" fn $i($($an : *const $at),*) { 149 | $(let $an : $at = unsafe { $an.read() };)* 150 | (|| { $($body)* })(); 151 | $(::core::mem::forget($an);)* 152 | 153 | } 154 | }; 155 | (@expand_rust_macro [$($a:tt)*] $i:ident [$($an:ident : $at:ty as $ac:tt),*] -> $rt:ty as $rc:tt {$($body:tt)*}) => { 156 | #[allow(non_snake_case)] 157 | #[allow(unused_unsafe)] 158 | #[allow(clippy::forget_copy, clippy::forget_ref)] 159 | #[doc(hidden)] 160 | $($a)* unsafe extern "C" fn $i($($an : *const $at, )* rt : *mut $rt) -> *mut $rt { 161 | 162 | $(let $an : $at = unsafe { $an.read() };)* 163 | { 164 | #[allow(unused_mut)] 165 | let mut lambda = || {$($body)*}; 166 | unsafe { ::core::ptr::write(rt, lambda()) }; 167 | } 168 | $(::core::mem::forget($an);)* 169 | rt 170 | } 171 | }; 172 | 173 | (@expand_rust_macro $($invalid:tt)*) => { 174 | compile_error!(concat!( "Cannot parse rust! macro: ", stringify!([ $($invalid)* ]) )) 175 | }; 176 | } 177 | 178 | /// This macro is used to embed arbitrary C++ code. 179 | /// 180 | /// There are two variants of the `cpp!` macro. The first variant is used for 181 | /// raw text inclusion. Text is included into the generated `C++` file in the 182 | /// order which they were defined, inlining module declarations. 183 | /// 184 | /// ```ignore 185 | /// cpp! {{ 186 | /// #include 187 | /// #include 188 | /// }} 189 | /// ``` 190 | /// 191 | /// The second variant is used to embed C++ code within Rust code. A list of 192 | /// variable names which should be captured are taken as the first argument, 193 | /// with their corresponding C++ type. The body is compiled as a C++ function. 194 | /// 195 | /// This variant of the macro may only be invoked in expression context, and 196 | /// requires an `unsafe` block, as it is performing FFI. 197 | /// 198 | /// ```ignore 199 | /// let y: i32 = 10; 200 | /// let mut z: i32 = 20; 201 | /// let x: i32 = unsafe { cpp!([y as "int32_t", mut z as "int32_t"] -> i32 as "int32_t" { 202 | /// z++; 203 | /// return y + z; 204 | /// })}; 205 | /// ``` 206 | /// 207 | /// You can also put the unsafe keyword as the first keyword of the `cpp!` macro, which 208 | /// has the same effect as putting the whole macro in an `unsafe` block: 209 | /// 210 | /// ```ignore 211 | /// let x: i32 = cpp!(unsafe [y as "int32_t", mut z as "int32_t"] -> i32 as "int32_t" { 212 | /// z++; 213 | /// return y + z; 214 | /// }); 215 | /// ``` 216 | /// 217 | /// ## rust! pseudo-macro 218 | /// 219 | /// The `cpp!` macro can contain, in the C++ code, a `rust!` sub-macro, which allows 220 | /// the inclusion of Rust code in C++ code. This is useful to 221 | /// implement callback or override virtual functions. Example: 222 | /// 223 | /// ```ignore 224 | /// trait MyTrait { 225 | /// fn compute_value(&self, x : i32) -> i32; 226 | /// } 227 | /// 228 | /// cpp!{{ 229 | /// struct TraitPtr { void *a,*b; }; 230 | /// class MyClassImpl : public MyClass { 231 | /// public: 232 | /// TraitPtr m_trait; 233 | /// int computeValue(int x) const override { 234 | /// return rust!(MCI_computeValue [m_trait : &MyTrait as "TraitPtr", x : i32 as "int"] 235 | /// -> i32 as "int" { 236 | /// m_trait.compute_value(x) 237 | /// }); 238 | /// } 239 | /// } 240 | /// }} 241 | /// ``` 242 | /// 243 | /// The syntax for the `rust!` macro is: 244 | /// ```ignore 245 | /// rust!($uniq_ident:ident [$($arg_name:ident : $arg_rust_type:ty as $arg_c_type:tt),*] 246 | /// $(-> $ret_rust_type:ty as $rust_c_type:tt)* {$($body:tt)*}) 247 | /// ``` 248 | /// `uniq_ident` is a unique identifier which will be used to name the `extern` function 249 | #[macro_export] 250 | macro_rules! cpp { 251 | // raw text inclusion 252 | ({$($body:tt)*}) => { $crate::__cpp_internal!{ @find_rust_macro [#[no_mangle] pub] $($body)*} }; 253 | 254 | // inline closure 255 | ([$($captures:tt)*] $($rest:tt)*) => { 256 | { 257 | $crate::__cpp_internal!{ @find_rust_macro [] $($rest)*} 258 | #[allow(unused)] 259 | #[derive($crate::__cpp_internal_closure)] 260 | enum CppClosureInput { 261 | Input = (stringify!([$($captures)*] $($rest)*), 0).1 262 | } 263 | __cpp_closure_impl![$($captures)*] 264 | } 265 | }; 266 | 267 | // wrap unsafe 268 | (unsafe $($tail:tt)*) => { unsafe { cpp!($($tail)*) } }; 269 | } 270 | 271 | #[doc(hidden)] 272 | pub trait CppTrait { 273 | type BaseType; 274 | const ARRAY_SIZE: usize; 275 | const CPP_TYPE: &'static str; 276 | } 277 | 278 | /// This macro allows wrapping a relocatable C++ struct or class that might have 279 | /// a destructor or copy constructor, implementing the `Drop` and `Clone` trait 280 | /// appropriately. 281 | /// 282 | /// ```ignore 283 | /// cpp_class!(pub unsafe struct MyClass as "MyClass"); 284 | /// impl MyClass { 285 | /// fn new() -> Self { 286 | /// unsafe { cpp!([] -> MyClass as "MyClass" { return MyClass(); }) } 287 | /// } 288 | /// fn member_function(&self, param : i32) -> i32 { 289 | /// unsafe { cpp!([self as "const MyClass*", param as "int"] -> i32 as "int" { 290 | /// return self->member_function(param); 291 | /// }) } 292 | /// } 293 | /// } 294 | /// ``` 295 | /// 296 | /// This will create a Rust struct `MyClass`, which has the same size and 297 | /// alignment as the C++ class `MyClass`. It will also implement the `Drop` trait 298 | /// calling the destructor, the `Clone` trait calling the copy constructor, if the 299 | /// class is copyable (or `Copy` if it is trivially copyable), and `Default` if the class 300 | /// is default constructible 301 | /// 302 | /// ## Derived Traits 303 | /// 304 | /// The `Default`, `Clone` and `Copy` traits are implicitly implemented if the C++ 305 | /// type has the corresponding constructors. 306 | /// 307 | /// You can add the `#[derive(...)]` attribute in the macro in order to get automatic 308 | /// implementation of the following traits: 309 | /// 310 | /// * The trait `PartialEq` will call the C++ `operator==`. 311 | /// * You can add the trait `Eq` if the semantics of the C++ operator are those of `Eq` 312 | /// * The trait `PartialOrd` need the C++ `operator<` for that type. `lt`, `le`, `gt` and 313 | /// `ge` will use the corresponding C++ operator if it is defined, otherwise it will 314 | /// fallback to the less than operator. For PartialOrd::partial_cmp, the `operator<` will 315 | /// be called twice. Note that it will never return `None`. 316 | /// * The trait `Ord` can also be specified when the semantics of the `operator<` corresponds 317 | /// to a total order 318 | /// 319 | /// ## Safety Warning 320 | /// 321 | /// Use of this macro is highly unsafe. Only certain C++ classes can be bound 322 | /// to, C++ classes may perform arbitrary unsafe operations, and invariants are 323 | /// easy to break. 324 | /// 325 | /// A notable restriction is that this macro only works if the C++ class is 326 | /// relocatable. 327 | /// 328 | /// ## Relocatable classes 329 | /// 330 | /// In order to be able to we wrapped the C++ class must be relocatable. That means 331 | /// that it can be moved in memory using `memcpy`. This restriction exists because 332 | /// safe Rust is allowed to move your types around. 333 | /// 334 | /// Most C++ types which do not contain self-references will be compatible, 335 | /// although this property cannot be statically checked by `rust-cpp`. 336 | /// All types that satisfy `std::is_trivially_copyable` are compatible. 337 | /// Maybe future version of the C++ standard would allow a compile-time check: 338 | /// [P1144](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1144r4.html) 339 | /// 340 | /// Unfortunately, as the STL often uses internal self-references for 341 | /// optimization purposes, such as the small-string optimization, this disallows 342 | /// most std:: classes. 343 | /// But `std::unique_ptr` and `std::shared_ptr` works. 344 | /// 345 | #[macro_export] 346 | macro_rules! cpp_class { 347 | ($(#[$($attrs:tt)*])* unsafe struct $name:ident as $type:expr) => { 348 | $crate::__cpp_class_internal!{@parse [ $(#[$($attrs)*])* ] [] [unsafe struct $name as $type] } 349 | }; 350 | ($(#[$($attrs:tt)*])* pub unsafe struct $name:ident as $type:expr) => { 351 | $crate::__cpp_class_internal!{@parse [ $(#[$($attrs)*])* ] [pub] [unsafe struct $name as $type] } 352 | }; 353 | ($(#[$($attrs:tt)*])* pub($($pub:tt)*) unsafe struct $name:ident as $type:expr) => { 354 | $crate::__cpp_class_internal!{@parse [ $(#[$($attrs)*])* ] [pub($($pub)*)] [unsafe struct $name as $type] } 355 | }; 356 | } 357 | 358 | /// Implementation details for cpp_class! 359 | #[doc(hidden)] 360 | #[macro_export] 361 | macro_rules! __cpp_class_internal { 362 | (@parse [$($attrs:tt)*] [$($vis:tt)*] [unsafe struct $name:ident as $type:expr]) => { 363 | $crate::__cpp_class_internal!{@parse_attributes [ $($attrs)* ] [] [ 364 | #[derive($crate::__cpp_internal_class)] 365 | #[repr(C)] 366 | $($vis)* struct $name { 367 | _opaque : [<$name as $crate::CppTrait>::BaseType ; <$name as $crate::CppTrait>::ARRAY_SIZE 368 | + (stringify!($($attrs)* $($vis)* unsafe struct $name as $type), 0).1] 369 | } 370 | ]} 371 | }; 372 | 373 | (@parse_attributes [] [$($attributes:tt)*] [$($result:tt)*]) => ( $($attributes)* $($result)* ); 374 | (@parse_attributes [#[derive($($der:ident),*)] $($tail:tt)* ] [$($attributes:tt)*] [$($result:tt)*] ) 375 | => ($crate::__cpp_class_internal!{@parse_derive [$($der),*] @parse_attributes [$($tail)*] [ $($attributes)* ] [ $($result)* ] } ); 376 | (@parse_attributes [ #[$m:meta] $($tail:tt)* ] [$($attributes:tt)*] [$($result:tt)*]) 377 | => ($crate::__cpp_class_internal!{@parse_attributes [$($tail)*] [$($attributes)* #[$m] ] [ $($result)* ] } ); 378 | 379 | (@parse_derive [] @parse_attributes $($result:tt)*) => ($crate::__cpp_class_internal!{@parse_attributes $($result)*} ); 380 | (@parse_derive [PartialEq $(,$tail:ident)*] $($result:tt)*) 381 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] $($result)*} ); 382 | (@parse_derive [PartialOrd $(,$tail:ident)*] $($result:tt)*) 383 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] $($result)*} ); 384 | (@parse_derive [Ord $(,$tail:ident)*] $($result:tt)*) 385 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] $($result)*} ); 386 | (@parse_derive [Default $(,$tail:ident)*] $($result:tt)*) 387 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] $($result)*} ); 388 | (@parse_derive [Clone $(,$tail:ident)*] $($result:tt)*) 389 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] $($result)*} ); 390 | (@parse_derive [Copy $(,$tail:ident)*] $($result:tt)*) 391 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] $($result)*} ); 392 | (@parse_derive [$i:ident $(,$tail:ident)*] @parse_attributes [$($attr:tt)*] [$($attributes:tt)*] [$($result:tt)*] ) 393 | => ( $crate::__cpp_class_internal!{@parse_derive [$($tail),*] @parse_attributes [$($attr)*] [$($attributes)* #[derive($i)] ] [ $($result)* ] } ); 394 | } 395 | -------------------------------------------------------------------------------- /cpp_build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp_build" 3 | version = "0.5.10" 4 | authors = ["Nika Layzell ", "Olivier Goffart "] 5 | edition = "2018" 6 | description = "Cargo build script for the `cpp` crate" 7 | readme = "../README.md" 8 | license = "MIT/Apache-2.0" 9 | keywords = ["c", "cxx", "ffi", "compiler", "build-dependencies"] 10 | categories = ["development-tools::ffi"] 11 | repository = "https://github.com/mystor/rust-cpp" 12 | documentation = "https://docs.rs/cpp_build" 13 | 14 | [features] 15 | # Ignore compilations error of the C++ code when building the documentation, as the docs.rs server 16 | # might not have the required libraries 17 | docs-only = [] 18 | parallel = ["cc/parallel"] 19 | 20 | [dependencies] 21 | lazy_static = "1.0" 22 | cc = "1.0.38" 23 | cpp_common = { path = "../cpp_common", version = "=0.5.10" } 24 | syn = { version = "2.0", features=["full", "visit"] } 25 | proc-macro2 = "1.0" 26 | regex = "1" 27 | unicode-xid = "0.2" 28 | 29 | [package.metadata.docs.rs] 30 | features = [ "docs-only" ] 31 | -------------------------------------------------------------------------------- /cpp_build/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate is the `cpp` cargo build script implementation. It is useless 2 | //! without the companion crates `cpp`, and `cpp_macro`. 3 | //! 4 | //! For more information, see the 5 | //! [`cpp` crate module level documentation](https://docs.rs/cpp). 6 | 7 | #![allow(clippy::write_with_newline)] 8 | 9 | mod strnom; 10 | 11 | use cpp_common::*; 12 | use lazy_static::lazy_static; 13 | use std::collections::hash_map::{Entry, HashMap}; 14 | use std::env; 15 | use std::fs::{create_dir, remove_dir_all, File}; 16 | use std::io::prelude::*; 17 | use std::path::{Path, PathBuf}; 18 | 19 | mod parser; 20 | 21 | fn warnln_impl(a: &str) { 22 | for s in a.lines() { 23 | println!("cargo:warning={}", s); 24 | } 25 | } 26 | 27 | macro_rules! warnln { 28 | ($($all:tt)*) => { 29 | $crate::warnln_impl(&format!($($all)*)); 30 | } 31 | } 32 | 33 | // Like the write! macro, but add the #line directive (pointing to this file). 34 | // Note: the string literal must be on on the same line of the macro 35 | macro_rules! write_add_line { 36 | ($o:expr, $($e:tt)*) => { 37 | (|| { 38 | writeln!($o, "#line {} \"{}\"", line!(), file!().replace('\\', "\\\\"))?; 39 | write!($o, $($e)*) 40 | })() 41 | }; 42 | } 43 | 44 | const INTERNAL_CPP_STRUCTS: &str = r#" 45 | /* THIS FILE IS GENERATED BY rust-cpp. DO NOT EDIT */ 46 | 47 | #include "stdint.h" // For {u}intN_t 48 | #include // For placement new 49 | #include // For abort 50 | #include 51 | #include 52 | 53 | namespace rustcpp { 54 | 55 | // We can't just pass or return any type from extern "C" rust functions (because the call 56 | // convention may differ between the C++ type, and the Rust type). 57 | // So we make sure to pass trivial structure that only contains a pointer to the object we want to 58 | // pass. The constructor of these helper class contains a 'container' of the right size which will 59 | // be allocated on the stack. 60 | template struct return_helper { 61 | struct container { 62 | #if defined (_MSC_VER) && (_MSC_VER + 0 < 1900) 63 | char memory[sizeof(T)]; 64 | ~container() { reinterpret_cast(this)->~T(); } 65 | #else 66 | // The fact that it is in an union means it is properly sized and aligned, but we have 67 | // to call the destructor and constructor manually 68 | union { T memory; }; 69 | ~container() { memory.~T(); } 70 | #endif 71 | container() {} 72 | }; 73 | const container* data; 74 | return_helper(int, const container &c = container()) : data(&c) { } 75 | }; 76 | 77 | template struct argument_helper { 78 | using type = const T&; 79 | }; 80 | template struct argument_helper { 81 | T &ref; 82 | argument_helper(T &x) : ref(x) {} 83 | using type = argument_helper const&; 84 | }; 85 | 86 | template 87 | typename std::enable_if::value>::type copy_helper(const void *src, void *dest) 88 | { new (dest) T (*static_cast(src)); } 89 | template 90 | typename std::enable_if::value>::type copy_helper(const void *, void *) 91 | { std::abort(); } 92 | template 93 | typename std::enable_if::value>::type default_helper(void *dest) 94 | { new (dest) T(); } 95 | template 96 | typename std::enable_if::value>::type default_helper(void *) 97 | { std::abort(); } 98 | 99 | template int compare_helper(const T &a, const T&b, int cmp) { 100 | switch (cmp) { 101 | using namespace std::rel_ops; 102 | case 0: 103 | if (a < b) 104 | return -1; 105 | if (b < a) 106 | return 1; 107 | return 0; 108 | case -2: return a < b; 109 | case 2: return a > b; 110 | case -1: return a <= b; 111 | case 1: return a >= b; 112 | } 113 | std::abort(); 114 | } 115 | } 116 | 117 | #define RUST_CPP_CLASS_HELPER(HASH, ...) \ 118 | extern "C" { \ 119 | void __cpp_destructor_##HASH(void *ptr) { typedef __VA_ARGS__ T; static_cast(ptr)->~T(); } \ 120 | void __cpp_copy_##HASH(const void *src, void *dest) { rustcpp::copy_helper<__VA_ARGS__>(src, dest); } \ 121 | void __cpp_default_##HASH(void *dest) { rustcpp::default_helper<__VA_ARGS__>(dest); } \ 122 | } 123 | "#; 124 | 125 | lazy_static! { 126 | static ref CPP_DIR: PathBuf = OUT_DIR.join("rust_cpp"); 127 | static ref CARGO_MANIFEST_DIR: PathBuf = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect( 128 | r#" 129 | -- rust-cpp fatal error -- 130 | 131 | The CARGO_MANIFEST_DIR environment variable was not set. 132 | NOTE: rust-cpp's build function must be run in a build script."# 133 | )); 134 | } 135 | 136 | fn gen_cpp_lib(visitor: &parser::Parser) -> PathBuf { 137 | let result_path = CPP_DIR.join("cpp_closures.cpp"); 138 | let mut output = File::create(&result_path).expect("Unable to generate temporary C++ file"); 139 | 140 | write!(output, "{}", INTERNAL_CPP_STRUCTS).unwrap(); 141 | 142 | if visitor.callbacks_count > 0 { 143 | #[rustfmt::skip] 144 | write_add_line!(output, r#" 145 | extern "C" {{ 146 | void (*rust_cpp_callbacks{file_hash}[{callbacks_count}])() = {{}}; 147 | }} 148 | "#, 149 | file_hash = *FILE_HASH, 150 | callbacks_count = visitor.callbacks_count 151 | ).unwrap(); 152 | } 153 | 154 | write!(output, "{}\n\n", &visitor.snippets).unwrap(); 155 | 156 | let mut hashmap = HashMap::new(); 157 | 158 | let mut sizealign = vec![]; 159 | for Closure { body_str, sig, callback_offset, .. } in &visitor.closures { 160 | let ClosureSig { captures, cpp, .. } = sig; 161 | 162 | let hash = sig.name_hash(); 163 | let name = sig.extern_name(); 164 | 165 | match hashmap.entry(hash) { 166 | Entry::Occupied(e) => { 167 | if *e.get() != sig { 168 | // Let the compiler do a compilation error. FIXME: report a better error 169 | warnln!("Hash collision detected."); 170 | } else { 171 | continue; 172 | } 173 | } 174 | Entry::Vacant(e) => { 175 | e.insert(sig); 176 | } 177 | } 178 | 179 | let is_void = cpp == "void"; 180 | 181 | // Generate the sizes array with the sizes of each of the argument types 182 | if is_void { 183 | sizealign.push(format!( 184 | "{{{hash}ull, 0, 1, {callback_offset}ull << 32}}", 185 | hash = hash, 186 | callback_offset = callback_offset 187 | )); 188 | } else { 189 | sizealign.push(format!("{{ 190 | {hash}ull, 191 | sizeof({type}), 192 | rustcpp::AlignOf<{type}>::value, 193 | rustcpp::Flags<{type}>::value | {callback_offset}ull << 32 194 | }}", hash=hash, type=cpp, callback_offset = callback_offset)); 195 | } 196 | for Capture { cpp, .. } in captures { 197 | sizealign.push(format!("{{ 198 | {hash}ull, 199 | sizeof({type}), 200 | rustcpp::AlignOf<{type}>::value, 201 | rustcpp::Flags<{type}>::value 202 | }}", hash=hash, type=cpp)); 203 | } 204 | 205 | // Generate the parameters and function declaration 206 | let params = captures 207 | .iter() 208 | .map(|&Capture { mutable, ref name, ref cpp }| { 209 | if mutable { 210 | format!("{} & {}", cpp, name) 211 | } else { 212 | format!("{} const& {}", cpp, name) 213 | } 214 | }) 215 | .collect::>() 216 | .join(", "); 217 | 218 | if is_void { 219 | #[rustfmt::skip] 220 | write_add_line!(output, r#" 221 | extern "C" {{ 222 | void {name}({params}) {{ 223 | {body} 224 | }} 225 | }} 226 | "#, 227 | name = &name, 228 | params = params, 229 | body = body_str 230 | ).unwrap(); 231 | } else { 232 | let comma = if params.is_empty() { "" } else { "," }; 233 | let args = captures 234 | .iter() 235 | .map(|Capture { name, .. }| name.to_string()) 236 | .collect::>() 237 | .join(", "); 238 | #[rustfmt::skip] 239 | write_add_line!(output, r#" 240 | static inline {ty} {name}_impl({params}) {{ 241 | {body} 242 | }} 243 | extern "C" {{ 244 | void {name}({params}{comma} void* __result) {{ 245 | ::new(__result) ({ty})({name}_impl({args})); 246 | }} 247 | }} 248 | "#, 249 | name = &name, 250 | params = params, 251 | comma = comma, 252 | ty = cpp, 253 | args = args, 254 | body = body_str 255 | ).unwrap(); 256 | } 257 | } 258 | 259 | for class in &visitor.classes { 260 | let hash = class.name_hash(); 261 | 262 | // Generate the sizes array 263 | sizealign.push(format!("{{ 264 | {hash}ull, 265 | sizeof({type}), 266 | rustcpp::AlignOf<{type}>::value, 267 | rustcpp::Flags<{type}>::value 268 | }}", hash=hash, type=class.cpp)); 269 | 270 | // Generate helper function. 271 | // (this is done in a macro, which right after a #line directing pointing to the location of 272 | // the cpp_class! macro in order to give right line information in the possible errors) 273 | write!( 274 | output, 275 | "{line}RUST_CPP_CLASS_HELPER({hash}, {cpp_name})\n", 276 | line = class.line, 277 | hash = hash, 278 | cpp_name = class.cpp 279 | ) 280 | .unwrap(); 281 | 282 | if class.derives("PartialEq") { 283 | write!(output, 284 | "{line}extern \"C\" bool __cpp_equal_{hash}(const {name} *a, const {name} *b) {{ return *a == *b; }}\n", 285 | line = class.line, hash = hash, name = class.cpp).unwrap(); 286 | } 287 | if class.derives("PartialOrd") { 288 | write!(output, 289 | "{line}extern \"C\" bool __cpp_compare_{hash}(const {name} *a, const {name} *b, int cmp) {{ return rustcpp::compare_helper(*a, *b, cmp); }}\n", 290 | line = class.line, hash = hash, name = class.cpp).unwrap(); 291 | } 292 | } 293 | 294 | let mut magic = vec![]; 295 | for mag in STRUCT_METADATA_MAGIC.iter() { 296 | magic.push(format!("{}", mag)); 297 | } 298 | 299 | #[rustfmt::skip] 300 | write_add_line!(output, r#" 301 | 302 | namespace rustcpp {{ 303 | 304 | template 305 | struct AlignOf {{ 306 | struct Inner {{ 307 | char a; 308 | T b; 309 | }}; 310 | static const uintptr_t value = sizeof(Inner) - sizeof(T); 311 | }}; 312 | 313 | template 314 | struct Flags {{ 315 | static const uintptr_t value = 316 | (std::is_copy_constructible::value << {flag_is_copy_constructible}) | 317 | (std::is_default_constructible::value << {flag_is_default_constructible}) | 318 | #if !defined(__GNUC__) || (__GNUC__ + 0 >= 5) || defined(__clang__) 319 | (std::is_trivially_destructible::value << {flag_is_trivially_destructible}) | 320 | (std::is_trivially_copyable::value << {flag_is_trivially_copyable}) | 321 | (std::is_trivially_default_constructible::value << {flag_is_trivially_default_constructible}) | 322 | #endif 323 | 0; 324 | }}; 325 | 326 | struct SizeAlign {{ 327 | uint64_t hash; 328 | uint64_t size; 329 | uint64_t align; 330 | uint64_t flags; 331 | }}; 332 | 333 | struct MetaData {{ 334 | uint8_t magic[128]; 335 | uint8_t version[16]; 336 | uint64_t endianness_check; 337 | uint64_t length; 338 | SizeAlign data[{length}]; 339 | }}; 340 | 341 | MetaData metadata_{hash} = {{ 342 | {{ {magic} }}, 343 | "{version}", 344 | 0xffef, 345 | {length}, 346 | {{ {data} }} 347 | }}; 348 | 349 | }} // namespace rustcpp 350 | "#, 351 | hash = *FILE_HASH, 352 | data = sizealign.join(", "), 353 | length = sizealign.len(), 354 | magic = magic.join(", "), 355 | version = VERSION, 356 | flag_is_copy_constructible = flags::IS_COPY_CONSTRUCTIBLE, 357 | flag_is_default_constructible = flags::IS_DEFAULT_CONSTRUCTIBLE, 358 | flag_is_trivially_destructible = flags::IS_TRIVIALLY_DESTRUCTIBLE, 359 | flag_is_trivially_copyable = flags::IS_TRIVIALLY_COPYABLE, 360 | flag_is_trivially_default_constructible = flags::IS_TRIVIALLY_DEFAULT_CONSTRUCTIBLE, 361 | ).unwrap(); 362 | 363 | result_path 364 | } 365 | 366 | fn clean_artifacts() { 367 | if CPP_DIR.is_dir() { 368 | remove_dir_all(&*CPP_DIR).expect( 369 | r#" 370 | -- rust-cpp fatal error -- 371 | 372 | Failed to remove existing build artifacts from output directory."#, 373 | ); 374 | } 375 | 376 | create_dir(&*CPP_DIR).expect( 377 | r#" 378 | -- rust-cpp fatal error -- 379 | 380 | Failed to create output object directory."#, 381 | ); 382 | } 383 | 384 | /// This struct is for advanced users of the build script. It allows providing 385 | /// configuration options to `cpp` and the compiler when it is used to build. 386 | /// 387 | /// ## API Note 388 | /// 389 | /// Internally, `cpp` uses the `cc` crate to build the compilation artifact, 390 | /// and many of the methods defined on this type directly proxy to an internal 391 | /// `cc::Build` object. 392 | pub struct Config { 393 | cc: cc::Build, 394 | std_flag_set: bool, // true if the -std flag was specified 395 | } 396 | 397 | impl Default for Config { 398 | fn default() -> Self { 399 | Config::new() 400 | } 401 | } 402 | 403 | /// Convert from a preconfigured [`cc::Build`] object to [`Config`]. 404 | /// Note that this will ensure C++ is enabled and add the proper include 405 | /// directories to work with `cpp`. 406 | impl From for Config { 407 | fn from(mut cc: cc::Build) -> Self { 408 | cc.cpp(true).include(&*CARGO_MANIFEST_DIR); 409 | Self { cc, std_flag_set: false } 410 | } 411 | } 412 | 413 | impl Config { 414 | /// Create a new `Config` object. This object will hold the configuration 415 | /// options which control the build. If you don't need to make any changes, 416 | /// `cpp_build::build` is a wrapper function around this interface. 417 | pub fn new() -> Config { 418 | cc::Build::new().into() 419 | } 420 | 421 | /// Add a directory to the `-I` or include path for headers 422 | pub fn include>(&mut self, dir: P) -> &mut Self { 423 | self.cc.include(dir); 424 | self 425 | } 426 | 427 | /// Specify a `-D` variable with an optional value 428 | pub fn define(&mut self, var: &str, val: Option<&str>) -> &mut Self { 429 | self.cc.define(var, val); 430 | self 431 | } 432 | 433 | // XXX: Make sure that this works with sizes logic 434 | /// Add an arbitrary object file to link in 435 | pub fn object>(&mut self, obj: P) -> &mut Self { 436 | self.cc.object(obj); 437 | self 438 | } 439 | 440 | /// Add an arbitrary flag to the invocation of the compiler 441 | pub fn flag(&mut self, flag: &str) -> &mut Self { 442 | if flag.starts_with("-std=") { 443 | self.std_flag_set = true; 444 | } 445 | self.cc.flag(flag); 446 | self 447 | } 448 | 449 | /// Add an arbitrary flag to the invocation of the compiler if it supports it 450 | pub fn flag_if_supported(&mut self, flag: &str) -> &mut Self { 451 | if flag.starts_with("-std=") { 452 | self.std_flag_set = true; 453 | } 454 | self.cc.flag_if_supported(flag); 455 | self 456 | } 457 | 458 | // XXX: Make sure this works with sizes logic 459 | /// Add a file which will be compiled 460 | pub fn file>(&mut self, p: P) -> &mut Self { 461 | self.cc.file(p); 462 | self 463 | } 464 | 465 | /// Set the standard library to link against when compiling with C++ 466 | /// support. 467 | /// 468 | /// The default value of this property depends on the current target: On 469 | /// OS X `Some("c++")` is used, when compiling for a Visual Studio based 470 | /// target `None` is used and for other targets `Some("stdc++")` is used. 471 | /// 472 | /// A value of `None` indicates that no automatic linking should happen, 473 | /// otherwise cargo will link against the specified library. 474 | /// 475 | /// The given library name must not contain the `lib` prefix. 476 | pub fn cpp_link_stdlib(&mut self, cpp_link_stdlib: Option<&str>) -> &mut Self { 477 | self.cc.cpp_link_stdlib(cpp_link_stdlib); 478 | self 479 | } 480 | 481 | /// Force the C++ compiler to use the specified standard library. 482 | /// 483 | /// Setting this option will automatically set `cpp_link_stdlib` to the same 484 | /// value. 485 | /// 486 | /// The default value of this option is always `None`. 487 | /// 488 | /// This option has no effect when compiling for a Visual Studio based 489 | /// target. 490 | /// 491 | /// This option sets the `-stdlib` flag, which is only supported by some 492 | /// compilers (clang, icc) but not by others (gcc). The library will not 493 | /// detect which compiler is used, as such it is the responsibility of the 494 | /// caller to ensure that this option is only used in conjuction with a 495 | /// compiler which supports the `-stdlib` flag. 496 | /// 497 | /// A value of `None` indicates that no specific C++ standard library should 498 | /// be used, otherwise `-stdlib` is added to the compile invocation. 499 | /// 500 | /// The given library name must not contain the `lib` prefix. 501 | pub fn cpp_set_stdlib(&mut self, cpp_set_stdlib: Option<&str>) -> &mut Self { 502 | self.cc.cpp_set_stdlib(cpp_set_stdlib); 503 | self 504 | } 505 | 506 | // XXX: Add support for custom targets 507 | // 508 | // /// Configures the target this configuration will be compiling for. 509 | // /// 510 | // /// This option is automatically scraped from the `TARGET` environment 511 | // /// variable by build scripts, so it's not required to call this function. 512 | // pub fn target(&mut self, target: &str) -> &mut Self { 513 | // self.cc.target(target); 514 | // self 515 | // } 516 | 517 | /// Configures the host assumed by this configuration. 518 | /// 519 | /// This option is automatically scraped from the `HOST` environment 520 | /// variable by build scripts, so it's not required to call this function. 521 | pub fn host(&mut self, host: &str) -> &mut Self { 522 | self.cc.host(host); 523 | self 524 | } 525 | 526 | /// Configures the optimization level of the generated object files. 527 | /// 528 | /// This option is automatically scraped from the `OPT_LEVEL` environment 529 | /// variable by build scripts, so it's not required to call this function. 530 | pub fn opt_level(&mut self, opt_level: u32) -> &mut Self { 531 | self.cc.opt_level(opt_level); 532 | self 533 | } 534 | 535 | /// Configures the optimization level of the generated object files. 536 | /// 537 | /// This option is automatically scraped from the `OPT_LEVEL` environment 538 | /// variable by build scripts, so it's not required to call this function. 539 | pub fn opt_level_str(&mut self, opt_level: &str) -> &mut Self { 540 | self.cc.opt_level_str(opt_level); 541 | self 542 | } 543 | 544 | /// Configures whether the compiler will emit debug information when 545 | /// generating object files. 546 | /// 547 | /// This option is automatically scraped from the `PROFILE` environment 548 | /// variable by build scripts (only enabled when the profile is "debug"), so 549 | /// it's not required to call this function. 550 | pub fn debug(&mut self, debug: bool) -> &mut Self { 551 | self.cc.debug(debug); 552 | self 553 | } 554 | 555 | // XXX: Add support for custom out_dir 556 | // 557 | // /// Configures the output directory where all object files and static 558 | // /// libraries will be located. 559 | // /// 560 | // /// This option is automatically scraped from the `OUT_DIR` environment 561 | // /// variable by build scripts, so it's not required to call this function. 562 | // pub fn out_dir>(&mut self, out_dir: P) -> &mut Self { 563 | // self.cc.out_dir(out_dir); 564 | // self 565 | // } 566 | 567 | /// Configures the compiler to be used to produce output. 568 | /// 569 | /// This option is automatically determined from the target platform or a 570 | /// number of environment variables, so it's not required to call this 571 | /// function. 572 | pub fn compiler>(&mut self, compiler: P) -> &mut Self { 573 | self.cc.compiler(compiler); 574 | self 575 | } 576 | 577 | /// Configures the tool used to assemble archives. 578 | /// 579 | /// This option is automatically determined from the target platform or a 580 | /// number of environment variables, so it's not required to call this 581 | /// function. 582 | pub fn archiver>(&mut self, archiver: P) -> &mut Self { 583 | self.cc.archiver(archiver); 584 | self 585 | } 586 | 587 | /// Define whether metadata should be emitted for cargo allowing it to 588 | /// automatically link the binary. Defaults to `true`. 589 | pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Self { 590 | // XXX: Use this to control the cargo metadata which rust-cpp produces 591 | self.cc.cargo_metadata(cargo_metadata); 592 | self 593 | } 594 | 595 | /// Configures whether the compiler will emit position independent code. 596 | /// 597 | /// This option defaults to `false` for `i686` and `windows-gnu` targets and 598 | /// to `true` for all other targets. 599 | pub fn pic(&mut self, pic: bool) -> &mut Self { 600 | self.cc.pic(pic); 601 | self 602 | } 603 | 604 | /// Extracts `cpp` declarations from the passed-in crate root, and builds 605 | /// the associated static library to be linked in to the final binary. 606 | /// 607 | /// This method does not perform rust codegen - that is performed by `cpp` 608 | /// and `cpp_macros`, which perform the actual procedural macro expansion. 609 | /// 610 | /// This method may technically be called more than once for ergonomic 611 | /// reasons, but that usually won't do what you want. Use a different 612 | /// `Config` object each time you want to build a crate. 613 | pub fn build>(&mut self, crate_root: P) { 614 | assert_eq!( 615 | env!("CARGO_PKG_VERSION"), 616 | VERSION, 617 | "Internal Error: mismatched cpp_common and cpp_build versions" 618 | ); 619 | 620 | // Clean up any leftover artifacts 621 | clean_artifacts(); 622 | 623 | // Parse the crate 624 | let mut visitor = parser::Parser::default(); 625 | if let Err(err) = visitor.parse_crate(crate_root.as_ref().to_owned()) { 626 | warnln!( 627 | r#"-- rust-cpp parse error -- 628 | There was an error parsing the crate for the rust-cpp build script: 629 | {} 630 | In order to provide a better error message, the build script will exit successfully, such that rustc can provide an error message."#, 631 | err 632 | ); 633 | return; 634 | } 635 | 636 | // Generate the C++ library code 637 | let filename = gen_cpp_lib(&visitor); 638 | 639 | // Ensure C++11 mode is enabled. We rely on some C++11 construct, so we 640 | // must enable C++11 by default. 641 | // MSVC, GCC >= 5, Clang >= 6 defaults to C++14, but since we want to 642 | // supports older compiler which defaults to C++98, we need to 643 | // explicitly set the "-std" flag. 644 | // Ideally should be done by https://github.com/alexcrichton/cc-rs/issues/191 645 | if !self.std_flag_set { 646 | self.cc.flag_if_supported("-std=c++11"); 647 | } 648 | // Build the C++ library 649 | if let Err(e) = self.cc.file(filename).try_compile(LIB_NAME) { 650 | let _ = writeln!(std::io::stderr(), "\n\nerror occurred: {}\n\n", e); 651 | #[cfg(not(feature = "docs-only"))] 652 | std::process::exit(1); 653 | } 654 | } 655 | } 656 | 657 | /// Run the `cpp` build process on the crate with a root at the given path. 658 | /// Intended to be used within `build.rs` files. 659 | pub fn build>(path: P) { 660 | Config::new().build(path) 661 | } 662 | -------------------------------------------------------------------------------- /cpp_build/src/parser.rs: -------------------------------------------------------------------------------- 1 | use cpp_common::{Class, Closure, Macro, RustInvocation}; 2 | use lazy_static::lazy_static; 3 | use regex::Regex; 4 | use std::fmt; 5 | use std::fs::File; 6 | use std::io::Read; 7 | use std::mem::swap; 8 | use std::path::{Path, PathBuf}; 9 | use syn::visit::Visit; 10 | 11 | #[allow(clippy::enum_variant_names)] 12 | #[derive(Debug)] 13 | pub enum Error { 14 | ParseCannotOpenFile { src_path: String }, 15 | ParseSyntaxError { src_path: String, error: syn::parse::Error }, 16 | LexError { src_path: String, line: u32 }, 17 | } 18 | 19 | impl fmt::Display for Error { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | match self { 22 | Error::ParseCannotOpenFile { ref src_path } => { 23 | write!(f, "Parsing crate: cannot open file `{}`.", src_path) 24 | } 25 | Error::ParseSyntaxError { ref src_path, ref error } => { 26 | write!(f, "Parsing file : `{}`:\n{}", src_path, error) 27 | } 28 | Error::LexError { ref src_path, ref line } => { 29 | write!(f, "{}:{}: Lexing error", src_path, line + 1) 30 | } 31 | } 32 | } 33 | } 34 | 35 | #[derive(Debug)] 36 | struct LineError(u32, String); 37 | 38 | impl LineError { 39 | fn add_line(self, a: u32) -> LineError { 40 | LineError(self.0 + a, self.1) 41 | } 42 | } 43 | 44 | impl fmt::Display for LineError { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | write!(f, "{}:{}", self.0 + 1, self.1) 47 | } 48 | } 49 | 50 | impl From for LineError { 51 | fn from(e: LexError) -> Self { 52 | LineError(e.line, "Lexing error".into()) 53 | } 54 | } 55 | 56 | enum ExpandSubMacroType<'a> { 57 | Lit, 58 | Closure(&'a mut u32), // the offset 59 | } 60 | 61 | // Given a string containing some C++ code with a rust! macro, 62 | // this functions expand the rust! macro to a call to an extern 63 | // function 64 | fn expand_sub_rust_macro(input: String, mut t: ExpandSubMacroType) -> Result { 65 | let mut result = input; 66 | let mut extra_decl = String::new(); 67 | let mut search_index = 0; 68 | 69 | loop { 70 | let (begin, end, line) = { 71 | let mut begin = 0; 72 | let mut cursor = new_cursor(&result); 73 | cursor.advance(search_index); 74 | while !cursor.is_empty() { 75 | cursor = skip_whitespace(cursor); 76 | let r = skip_literal(cursor)?; 77 | cursor = r.0; 78 | if r.1 { 79 | continue; 80 | } 81 | if cursor.is_empty() { 82 | break; 83 | } 84 | if let Ok((cur, ident)) = symbol(cursor) { 85 | begin = cursor.off as usize; 86 | cursor = cur; 87 | if ident != "rust" { 88 | continue; 89 | } 90 | } else { 91 | cursor = cursor.advance(1); 92 | continue; 93 | } 94 | cursor = skip_whitespace(cursor); 95 | if !cursor.starts_with("!") { 96 | continue; 97 | } 98 | break; 99 | } 100 | if cursor.is_empty() { 101 | return Ok(extra_decl + &result); 102 | } 103 | let end = find_delimited((find_delimited(cursor, "(")?.0).advance(1), ")")?.0; 104 | (begin, end.off as usize + 1, cursor.line) 105 | }; 106 | let input: ::proc_macro2::TokenStream = result[begin..end] 107 | .parse() 108 | .map_err(|_| LineError(line, "TokenStream parse error".into()))?; 109 | let rust_invocation = 110 | ::syn::parse2::(input).map_err(|e| LineError(line, e.to_string()))?; 111 | let fn_name = match t { 112 | ExpandSubMacroType::Lit => { 113 | extra_decl.push_str(&format!("extern \"C\" void {}();\n", rust_invocation.id)); 114 | rust_invocation.id.clone().to_string() 115 | } 116 | ExpandSubMacroType::Closure(ref mut offset) => { 117 | use cpp_common::FILE_HASH; 118 | **offset += 1; 119 | format!( 120 | "rust_cpp_callbacks{file_hash}[{offset}]", 121 | file_hash = *FILE_HASH, 122 | offset = **offset - 1 123 | ) 124 | } 125 | }; 126 | 127 | let mut decl_types = rust_invocation 128 | .arguments 129 | .iter() 130 | .map(|(_, val)| format!("rustcpp::argument_helper<{}>::type", val)) 131 | .collect::>(); 132 | let mut call_args = 133 | rust_invocation.arguments.iter().map(|(val, _)| val.to_string()).collect::>(); 134 | 135 | let fn_call = match rust_invocation.return_type { 136 | None => format!( 137 | "reinterpret_cast({f})({args})", 138 | f = fn_name, 139 | types = decl_types.join(", "), 140 | args = call_args.join(", ") 141 | ), 142 | Some(rty) => { 143 | decl_types.push(format!("rustcpp::return_helper<{rty}>", rty = rty)); 144 | call_args.push("0".to_string()); 145 | format!( 146 | "std::move(*reinterpret_cast<{rty}*(*)({types})>({f})({args}))", 147 | rty = rty, 148 | f = fn_name, 149 | types = decl_types.join(", "), 150 | args = call_args.join(", ") 151 | ) 152 | } 153 | }; 154 | 155 | let fn_call = { 156 | // remove the rust! macro from the C++ snippet 157 | let orig = result.drain(begin..end); 158 | // add \ņ to the invocation in order to keep the same amount of line numbers 159 | // so errors point to the right line. 160 | orig.filter(|x| *x == '\n').fold(fn_call, |mut res, _| { 161 | res.push('\n'); 162 | res 163 | }) 164 | }; 165 | // add the invocation of call where the rust! macro used to be. 166 | result.insert_str(begin, &fn_call); 167 | search_index = begin + fn_call.len(); 168 | } 169 | } 170 | 171 | #[test] 172 | fn test_expand_sub_rust_macro() { 173 | let x = expand_sub_rust_macro("{ rust!(xxx [] { 1 }); }".to_owned(), ExpandSubMacroType::Lit); 174 | assert_eq!(x.unwrap(), "extern \"C\" void xxx();\n{ reinterpret_cast(xxx)(); }"); 175 | 176 | let x = expand_sub_rust_macro( 177 | "{ hello( rust!(xxx [] { 1 }), rust!(yyy [] { 2 }); ) }".to_owned(), 178 | ExpandSubMacroType::Lit, 179 | ); 180 | assert_eq!(x.unwrap(), "extern \"C\" void xxx();\nextern \"C\" void yyy();\n{ hello( reinterpret_cast(xxx)(), reinterpret_cast(yyy)(); ) }"); 181 | 182 | let s = "{ /* rust! */ /* rust!(xxx [] { 1 }) */ }".to_owned(); 183 | assert_eq!(expand_sub_rust_macro(s.clone(), ExpandSubMacroType::Lit).unwrap(), s); 184 | } 185 | 186 | #[path = "strnom.rs"] 187 | mod strnom; 188 | use crate::strnom::*; 189 | 190 | fn skip_literal(mut input: Cursor) -> PResult { 191 | //input = whitespace(input)?.0; 192 | if input.starts_with("\"") { 193 | input = cooked_string(input.advance(1))?.0; 194 | debug_assert!(input.starts_with("\"")); 195 | return Ok((input.advance(1), true)); 196 | } 197 | if input.starts_with("b\"") { 198 | input = cooked_byte_string(input.advance(2))?.0; 199 | debug_assert!(input.starts_with("\"")); 200 | return Ok((input.advance(1), true)); 201 | } 202 | if input.starts_with("\'") { 203 | input = input.advance(1); 204 | let cur = cooked_char(input)?.0; 205 | if !cur.starts_with("\'") { 206 | return Ok((symbol(input)?.0, true)); 207 | } 208 | return Ok((cur.advance(1), true)); 209 | } 210 | if input.starts_with("b\'") { 211 | input = cooked_byte(input.advance(2))?.0; 212 | if !input.starts_with("\'") { 213 | return Err(LexError { line: input.line }); 214 | } 215 | return Ok((input.advance(1), true)); 216 | } 217 | lazy_static! { 218 | static ref RAW: Regex = Regex::new(r##"^b?r#*""##).unwrap(); 219 | } 220 | if RAW.is_match(input.rest) { 221 | let q = input.rest.find('r').unwrap(); 222 | input = input.advance(q + 1); 223 | return raw_string(input).map(|x| (x.0, true)); 224 | } 225 | Ok((input, false)) 226 | } 227 | 228 | fn new_cursor(s: &str) -> Cursor { 229 | Cursor { rest: s, off: 0, line: 0, column: 0 } 230 | } 231 | 232 | #[test] 233 | fn test_skip_literal() -> Result<(), LexError> { 234 | assert!((skip_literal(new_cursor(r#""fofofo"ok xx"#))?.0).starts_with("ok")); 235 | assert!((skip_literal(new_cursor(r#""kk\"kdk"ok xx"#))?.0).starts_with("ok")); 236 | assert!((skip_literal(new_cursor("r###\"foo \" bar \\\" \"###ok xx"))?.0).starts_with("ok")); 237 | assert!( 238 | (skip_literal(new_cursor("br###\"foo 'jjk' \" bar \\\" \"###ok xx"))?.0).starts_with("ok") 239 | ); 240 | assert!((skip_literal(new_cursor("'4'ok xx"))?.0).starts_with("ok")); 241 | assert!((skip_literal(new_cursor("'\''ok xx"))?.0).starts_with("ok")); 242 | assert!((skip_literal(new_cursor("b'\''ok xx"))?.0).starts_with("ok")); 243 | assert!((skip_literal(new_cursor("'abc ok xx"))?.0).starts_with(" ok")); 244 | assert!((skip_literal(new_cursor("'a ok xx"))?.0).starts_with(" ok")); 245 | 246 | assert!((skip_whitespace(new_cursor("ok xx"))).starts_with("ok")); 247 | assert!((skip_whitespace(new_cursor(" ok xx"))).starts_with("ok")); 248 | assert!((skip_whitespace(new_cursor(" \n /* /*dd \n // */ */ // foo \n ok xx/* */"))) 249 | .starts_with("ok")); 250 | 251 | Ok(()) 252 | } 253 | 254 | // advance the cursor until it finds the needle. 255 | fn find_delimited<'a>(mut input: Cursor<'a>, needle: &str) -> PResult<'a, ()> { 256 | let mut stack: Vec<&'static str> = vec![]; 257 | while !input.is_empty() { 258 | input = skip_whitespace(input); 259 | input = skip_literal(input)?.0; 260 | if input.is_empty() { 261 | break; 262 | } 263 | if stack.is_empty() && input.starts_with(needle) { 264 | return Ok((input, ())); 265 | } else if stack.last().map_or(false, |x| input.starts_with(x)) { 266 | stack.pop(); 267 | } else if input.starts_with("(") { 268 | stack.push(")"); 269 | } else if input.starts_with("[") { 270 | stack.push("]"); 271 | } else if input.starts_with("{") { 272 | stack.push("}"); 273 | } else if input.starts_with(")") || input.starts_with("]") || input.starts_with("}") { 274 | return Err(LexError { line: input.line }); 275 | } 276 | input = input.advance(1); 277 | } 278 | Err(LexError { line: input.line }) 279 | } 280 | 281 | #[test] 282 | fn test_find_delimited() -> Result<(), LexError> { 283 | assert!((find_delimited(new_cursor(" x f ok"), "f")?.0).starts_with("f ok")); 284 | assert!((find_delimited(new_cursor(" {f} f ok"), "f")?.0).starts_with("f ok")); 285 | assert!((find_delimited(new_cursor(" (f\")\" { ( ) } /* ) */ f ) f ok"), "f")?.0) 286 | .starts_with("f ok")); 287 | Ok(()) 288 | } 289 | 290 | #[test] 291 | fn test_cursor_advance() -> Result<(), LexError> { 292 | assert_eq!(new_cursor("\n\n\n").advance(2).line, 2); 293 | assert_eq!(new_cursor("\n \n\n").advance(2).line, 1); 294 | assert_eq!(new_cursor("\n\n\n").advance(2).column, 0); 295 | assert_eq!(new_cursor("\n \n\n").advance(2).column, 1); 296 | 297 | assert_eq!((find_delimited(new_cursor("\n/*\n \n */ ( \n ) /* */ f"), "f")?.0).line, 4); 298 | assert_eq!((find_delimited(new_cursor("\n/*\n \n */ ( \n ) /* */ f"), "f")?.0).column, 9); 299 | Ok(()) 300 | } 301 | 302 | fn line_directive(path: &Path, cur: Cursor) -> String { 303 | let mut line = 304 | format!("#line {} \"{}\"\n", cur.line + 1, path.to_string_lossy().replace('\\', "\\\\")); 305 | for _ in 0..cur.column { 306 | line.push(' '); 307 | } 308 | line 309 | } 310 | 311 | #[derive(Default)] 312 | pub struct Parser { 313 | pub closures: Vec, 314 | pub classes: Vec, 315 | pub snippets: String, 316 | pub callbacks_count: u32, 317 | current_path: PathBuf, // The current file being parsed 318 | mod_dir: PathBuf, 319 | mod_error: Option, // An error occuring while visiting the modules 320 | } 321 | 322 | impl Parser { 323 | pub fn parse_crate(&mut self, crate_root: PathBuf) -> Result<(), Error> { 324 | let parent = crate_root.parent().map(|x| x.to_owned()).unwrap_or_default(); 325 | self.parse_mod(crate_root, parent) 326 | } 327 | 328 | fn parse_mod(&mut self, mod_path: PathBuf, submod_dir: PathBuf) -> Result<(), Error> { 329 | let mut s = String::new(); 330 | let mut f = File::open(&mod_path).map_err(|_| Error::ParseCannotOpenFile { 331 | src_path: mod_path.to_str().unwrap().to_owned(), 332 | })?; 333 | f.read_to_string(&mut s).map_err(|_| Error::ParseCannotOpenFile { 334 | src_path: mod_path.to_str().unwrap().to_owned(), 335 | })?; 336 | 337 | let fi = syn::parse_file(&s).map_err(|x| Error::ParseSyntaxError { 338 | src_path: mod_path.to_str().unwrap().to_owned(), 339 | error: x, 340 | })?; 341 | 342 | let mut current_path = mod_path; 343 | let mut mod_dir = submod_dir; 344 | 345 | swap(&mut self.current_path, &mut current_path); 346 | swap(&mut self.mod_dir, &mut mod_dir); 347 | 348 | self.find_cpp_macros(&s)?; 349 | self.visit_file(&fi); 350 | if let Some(err) = self.mod_error.take() { 351 | return Err(err); 352 | } 353 | 354 | swap(&mut self.current_path, &mut current_path); 355 | swap(&mut self.mod_dir, &mut mod_dir); 356 | 357 | Ok(()) 358 | } 359 | 360 | /* 361 | fn parse_macro(&mut self, tts: TokenStream) { 362 | let mut last_ident: Option = None; 363 | let mut is_macro = false; 364 | for t in tts.into_iter() { 365 | match t { 366 | TokenTree::Punct(ref p) if p.as_char() == '!' => is_macro = true, 367 | TokenTree::Ident(i) => { 368 | is_macro = false; 369 | last_ident = Some(i); 370 | } 371 | TokenTree::Group(d) => { 372 | if is_macro && last_ident.as_ref().map_or(false, |i| i == "cpp") { 373 | self.handle_cpp(&d.stream()) 374 | } else if is_macro && last_ident.as_ref().map_or(false, |i| i == "cpp_class") { 375 | self.handle_cpp_class(&d.stream()) 376 | } else { 377 | self.parse_macro(d.stream()) 378 | } 379 | is_macro = false; 380 | last_ident = None; 381 | } 382 | _ => { 383 | is_macro = false; 384 | last_ident = None; 385 | } 386 | } 387 | } 388 | } 389 | */ 390 | 391 | fn find_cpp_macros(&mut self, source: &str) -> Result<(), Error> { 392 | let mut cursor = new_cursor(source); 393 | while !cursor.is_empty() { 394 | cursor = skip_whitespace(cursor); 395 | let r = skip_literal(cursor).map_err(|e| self.lex_error(e))?; 396 | cursor = r.0; 397 | if r.1 { 398 | continue; 399 | } 400 | if let Ok((cur, ident)) = symbol(cursor) { 401 | cursor = cur; 402 | if ident != "cpp" && ident != "cpp_class" { 403 | continue; 404 | } 405 | cursor = skip_whitespace(cursor); 406 | if !cursor.starts_with("!") { 407 | continue; 408 | } 409 | cursor = skip_whitespace(cursor.advance(1)); 410 | let delim = if cursor.starts_with("(") { 411 | ")" 412 | } else if cursor.starts_with("[") { 413 | "]" 414 | } else if cursor.starts_with("{") { 415 | "}" 416 | } else { 417 | continue; 418 | }; 419 | cursor = cursor.advance(1); 420 | let mut macro_cur = cursor; 421 | cursor = find_delimited(cursor, delim).map_err(|e| self.lex_error(e))?.0; 422 | let size = (cursor.off - macro_cur.off) as usize; 423 | macro_cur.rest = ¯o_cur.rest[..size]; 424 | if ident == "cpp" { 425 | self.handle_cpp(macro_cur).unwrap_or_else(|e| { 426 | panic!("Error while parsing cpp! macro:\n{:?}:{}", self.current_path, e) 427 | }); 428 | } else { 429 | debug_assert_eq!(ident, "cpp_class"); 430 | self.handle_cpp_class(macro_cur).unwrap_or_else(|e| { 431 | panic!( 432 | "Error while parsing cpp_class! macro:\n{:?}:{}", 433 | self.current_path, e 434 | ) 435 | }); 436 | } 437 | continue; 438 | } 439 | if cursor.is_empty() { 440 | break; 441 | } 442 | cursor = cursor.advance(1); // Not perfect, but should work 443 | } 444 | Ok(()) 445 | } 446 | 447 | fn lex_error(&self, e: LexError) -> Error { 448 | Error::LexError { 449 | src_path: self.current_path.clone().to_str().unwrap().to_owned(), 450 | line: e.line, 451 | } 452 | } 453 | 454 | fn handle_cpp(&mut self, x: Cursor) -> Result<(), LineError> { 455 | // Since syn don't give the exact string, we extract manually 456 | let begin = (find_delimited(x, "{")?.0).advance(1); 457 | let end = find_delimited(begin, "}")?.0; 458 | let extracted = &begin.rest[..(end.off - begin.off) as usize]; 459 | 460 | let input: ::proc_macro2::TokenStream = 461 | x.rest.parse().map_err(|_| LineError(x.line, "TokenStream parse error".into()))?; 462 | match ::syn::parse2::(input).map_err(|e| LineError(x.line, e.to_string()))? { 463 | Macro::Closure(mut c) => { 464 | c.callback_offset = self.callbacks_count; 465 | c.body_str = line_directive(&self.current_path, begin) 466 | + &expand_sub_rust_macro( 467 | extracted.to_string(), 468 | ExpandSubMacroType::Closure(&mut self.callbacks_count), 469 | ) 470 | .map_err(|e| e.add_line(begin.line))?; 471 | self.closures.push(c); 472 | } 473 | Macro::Lit(_l) => { 474 | self.snippets.push('\n'); 475 | let snip = expand_sub_rust_macro( 476 | line_directive(&self.current_path, begin) + extracted, 477 | ExpandSubMacroType::Lit, 478 | ) 479 | .map_err(|e| e.add_line(begin.line))?; 480 | self.snippets.push_str(&snip); 481 | } 482 | } 483 | Ok(()) 484 | } 485 | 486 | fn handle_cpp_class(&mut self, x: Cursor) -> Result<(), LineError> { 487 | let input: ::proc_macro2::TokenStream = 488 | x.rest.parse().map_err(|_| LineError(x.line, "TokenStream parse error".into()))?; 489 | let mut class = 490 | ::syn::parse2::(input).map_err(|e| LineError(x.line, e.to_string()))?; 491 | class.line = line_directive(&self.current_path, x); 492 | self.classes.push(class); 493 | Ok(()) 494 | } 495 | } 496 | 497 | impl<'ast> Visit<'ast> for Parser { 498 | /* This is currently commented out because proc_macro2 don't allow us to get the text verbatim 499 | (https://github.com/alexcrichton/proc-macro2/issues/110#issuecomment-411959999) 500 | fn visit_macro(&mut self, mac: &syn::Macro) { 501 | if mac.path.segments.len() != 1 { 502 | return; 503 | } 504 | if mac.path.segments[0].ident == "cpp" { 505 | self.handle_cpp(&mac.tts); 506 | } else if mac.path.segments[0].ident == "cpp_class" { 507 | self.handle_cpp_class(&mac.tts); 508 | } else { 509 | self.parse_macro(mac.tts.clone()); 510 | } 511 | }*/ 512 | 513 | fn visit_item_mod(&mut self, item: &'ast syn::ItemMod) { 514 | if self.mod_error.is_some() { 515 | return; 516 | } 517 | 518 | if item.content.is_some() { 519 | let mut parent = self.mod_dir.join(item.ident.to_string()); 520 | swap(&mut self.mod_dir, &mut parent); 521 | syn::visit::visit_item_mod(self, item); 522 | swap(&mut self.mod_dir, &mut parent); 523 | return; 524 | } 525 | 526 | let mut cfg_disabled = false; 527 | 528 | // Determine the path of the inner module's file 529 | for attr in &item.attrs { 530 | match &attr.meta { 531 | // parse #[path = "foo.rs"]: read module from the specified path 532 | syn::Meta::NameValue(syn::MetaNameValue { 533 | path, 534 | value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }), 535 | .. 536 | }) if path.is_ident("path") => { 537 | let mod_path = self.mod_dir.join(s.value()); 538 | let parent = self.mod_dir.parent().map(|x| x.to_owned()).unwrap_or_default(); 539 | return self 540 | .parse_mod(mod_path, parent) 541 | .unwrap_or_else(|err| self.mod_error = Some(err)); 542 | } 543 | // parse #[cfg(feature = "feature")]: don't follow modules not enabled by current features 544 | syn::Meta::List(list @ syn::MetaList { path, .. }) if path.is_ident("cfg") => { 545 | drop(list.parse_nested_meta(|meta| { 546 | if meta.path.is_ident("feature") { 547 | let feature: syn::LitStr = meta.value()?.parse()?; 548 | let feature_env_var = "CARGO_FEATURE_".to_owned() 549 | + &feature.value().to_uppercase().replace('-', "_"); 550 | if std::env::var_os(feature_env_var).is_none() { 551 | cfg_disabled = true; 552 | } 553 | } 554 | Ok(()) 555 | })) 556 | } 557 | _ => {} 558 | } 559 | } 560 | 561 | if cfg_disabled { 562 | return; 563 | } 564 | 565 | let mod_name = item.ident.to_string(); 566 | let subdir = self.mod_dir.join(&mod_name); 567 | let subdir_mod = subdir.join("mod.rs"); 568 | if subdir_mod.is_file() { 569 | return self 570 | .parse_mod(subdir_mod, subdir) 571 | .unwrap_or_else(|err| self.mod_error = Some(err)); 572 | } 573 | 574 | let adjacent = self.mod_dir.join(format!("{}.rs", mod_name)); 575 | if adjacent.is_file() { 576 | return self 577 | .parse_mod(adjacent, subdir) 578 | .unwrap_or_else(|err| self.mod_error = Some(err)); 579 | } 580 | 581 | panic!( 582 | "No file with module definition for `mod {}` in file {:?}", 583 | mod_name, self.current_path 584 | ); 585 | } 586 | } 587 | -------------------------------------------------------------------------------- /cpp_build/src/strnom.rs: -------------------------------------------------------------------------------- 1 | //! Fork of the equivalent file from the proc-macro2 file. 2 | //! Modified to support line number counting in Cursor. 3 | //! Also contains some function from stable.rs of proc_macro2. 4 | 5 | #![allow(dead_code)] // Why is this needed ? 6 | 7 | use std::str::{Bytes, CharIndices, Chars}; 8 | 9 | use unicode_xid::UnicodeXID; 10 | 11 | #[derive(Debug)] 12 | pub struct LexError { 13 | pub line: u32, 14 | } 15 | 16 | #[derive(Copy, Clone, Eq, PartialEq)] 17 | pub struct Cursor<'a> { 18 | pub rest: &'a str, 19 | pub off: u32, 20 | pub line: u32, 21 | pub column: u32, 22 | } 23 | 24 | impl<'a> Cursor<'a> { 25 | #[allow(clippy::suspicious_map)] 26 | pub fn advance(&self, amt: usize) -> Cursor<'a> { 27 | let mut column_start: Option = None; 28 | Cursor { 29 | rest: &self.rest[amt..], 30 | off: self.off + (amt as u32), 31 | line: self.line 32 | + self.rest[..amt] 33 | .char_indices() 34 | .filter(|(_, x)| *x == '\n') 35 | .map(|(i, _)| { 36 | column_start = Some(i); 37 | }) 38 | .count() as u32, 39 | column: match column_start { 40 | None => self.column + (amt as u32), 41 | Some(i) => (amt - i) as u32 - 1, 42 | }, 43 | } 44 | } 45 | 46 | pub fn find(&self, p: char) -> Option { 47 | self.rest.find(p) 48 | } 49 | 50 | pub fn starts_with(&self, s: &str) -> bool { 51 | self.rest.starts_with(s) 52 | } 53 | 54 | pub fn is_empty(&self) -> bool { 55 | self.rest.is_empty() 56 | } 57 | 58 | pub fn len(&self) -> usize { 59 | self.rest.len() 60 | } 61 | 62 | pub fn as_bytes(&self) -> &'a [u8] { 63 | self.rest.as_bytes() 64 | } 65 | 66 | pub fn bytes(&self) -> Bytes<'a> { 67 | self.rest.bytes() 68 | } 69 | 70 | pub fn chars(&self) -> Chars<'a> { 71 | self.rest.chars() 72 | } 73 | 74 | pub fn char_indices(&self) -> CharIndices<'a> { 75 | self.rest.char_indices() 76 | } 77 | } 78 | 79 | pub type PResult<'a, O> = Result<(Cursor<'a>, O), LexError>; 80 | 81 | pub fn whitespace(input: Cursor) -> PResult<()> { 82 | if input.is_empty() { 83 | return Err(LexError { line: input.line }); 84 | } 85 | 86 | let bytes = input.as_bytes(); 87 | let mut i = 0; 88 | while i < bytes.len() { 89 | let s = input.advance(i); 90 | if bytes[i] == b'/' { 91 | if s.starts_with("//") 92 | // && (!s.starts_with("///") || s.starts_with("////")) 93 | // && !s.starts_with("//!") 94 | { 95 | if let Some(len) = s.find('\n') { 96 | i += len + 1; 97 | continue; 98 | } 99 | break; 100 | } else if s.starts_with("/**/") { 101 | i += 4; 102 | continue; 103 | } else if s.starts_with("/*") 104 | // && (!s.starts_with("/**") || s.starts_with("/***")) 105 | // && !s.starts_with("/*!") 106 | { 107 | let (_, com) = block_comment(s)?; 108 | i += com.len(); 109 | continue; 110 | } 111 | } 112 | match bytes[i] { 113 | b' ' | 0x09..=0x0d => { 114 | i += 1; 115 | continue; 116 | } 117 | b if b <= 0x7f => {} 118 | _ => { 119 | let ch = s.chars().next().unwrap(); 120 | if is_whitespace(ch) { 121 | i += ch.len_utf8(); 122 | continue; 123 | } 124 | } 125 | } 126 | return if i > 0 { Ok((s, ())) } else { Err(LexError { line: s.line }) }; 127 | } 128 | Ok((input.advance(input.len()), ())) 129 | } 130 | 131 | pub fn block_comment(input: Cursor) -> PResult<&str> { 132 | if !input.starts_with("/*") { 133 | return Err(LexError { line: input.line }); 134 | } 135 | 136 | let mut depth = 0; 137 | let bytes = input.as_bytes(); 138 | let mut i = 0; 139 | let upper = bytes.len() - 1; 140 | while i < upper { 141 | if bytes[i] == b'/' && bytes[i + 1] == b'*' { 142 | depth += 1; 143 | i += 1; // eat '*' 144 | } else if bytes[i] == b'*' && bytes[i + 1] == b'/' { 145 | depth -= 1; 146 | if depth == 0 { 147 | return Ok((input.advance(i + 2), &input.rest[..i + 2])); 148 | } 149 | i += 1; // eat '/' 150 | } 151 | i += 1; 152 | } 153 | Err(LexError { line: input.line }) 154 | } 155 | 156 | pub fn skip_whitespace(input: Cursor) -> Cursor { 157 | match whitespace(input) { 158 | Ok((rest, _)) => rest, 159 | Err(_) => input, 160 | } 161 | } 162 | 163 | fn is_whitespace(ch: char) -> bool { 164 | // Rust treats left-to-right mark and right-to-left mark as whitespace 165 | ch.is_whitespace() || ch == '\u{200e}' || ch == '\u{200f}' 166 | } 167 | 168 | // --- functions from stable.rs 169 | 170 | #[inline] 171 | fn is_ident_start(c: char) -> bool { 172 | c.is_ascii_alphabetic() || c == '_' || (c > '\x7f' && UnicodeXID::is_xid_start(c)) 173 | } 174 | 175 | #[inline] 176 | fn is_ident_continue(c: char) -> bool { 177 | c.is_ascii_alphanumeric() || c == '_' || (c > '\x7f' && UnicodeXID::is_xid_continue(c)) 178 | } 179 | 180 | pub fn symbol(input: Cursor) -> PResult<&str> { 181 | let mut chars = input.char_indices(); 182 | 183 | let raw = input.starts_with("r#"); 184 | if raw { 185 | chars.next(); 186 | chars.next(); 187 | } 188 | 189 | match chars.next() { 190 | Some((_, ch)) if is_ident_start(ch) => {} 191 | _ => return Err(LexError { line: input.line }), 192 | } 193 | 194 | let mut end = input.len(); 195 | for (i, ch) in chars { 196 | if !is_ident_continue(ch) { 197 | end = i; 198 | break; 199 | } 200 | } 201 | 202 | let a = &input.rest[..end]; 203 | if a == "r#_" { 204 | Err(LexError { line: input.line }) 205 | } else { 206 | let ident = if raw { &a[2..] } else { a }; 207 | Ok((input.advance(end), ident)) 208 | } 209 | } 210 | 211 | pub fn cooked_string(input: Cursor) -> PResult<()> { 212 | let mut chars = input.char_indices().peekable(); 213 | while let Some((byte_offset, ch)) = chars.next() { 214 | match ch { 215 | '"' => { 216 | return Ok((input.advance(byte_offset), ())); 217 | } 218 | '\r' => { 219 | if let Some((_, '\n')) = chars.next() { 220 | // ... 221 | } else { 222 | break; 223 | } 224 | } 225 | '\\' => match chars.next() { 226 | Some((_, 'x')) => { 227 | if !backslash_x_char(&mut chars) { 228 | break; 229 | } 230 | } 231 | Some((_, 'n')) | Some((_, 'r')) | Some((_, 't')) | Some((_, '\\')) 232 | | Some((_, '\'')) | Some((_, '"')) | Some((_, '0')) => {} 233 | Some((_, 'u')) => { 234 | if !backslash_u(&mut chars) { 235 | break; 236 | } 237 | } 238 | Some((_, '\n')) | Some((_, '\r')) => { 239 | while let Some(&(_, ch)) = chars.peek() { 240 | if ch.is_whitespace() { 241 | chars.next(); 242 | } else { 243 | break; 244 | } 245 | } 246 | } 247 | _ => break, 248 | }, 249 | _ch => {} 250 | } 251 | } 252 | Err(LexError { line: input.line }) 253 | } 254 | 255 | pub fn cooked_byte_string(mut input: Cursor) -> PResult<()> { 256 | let mut bytes = input.bytes().enumerate(); 257 | 'outer: while let Some((offset, b)) = bytes.next() { 258 | match b { 259 | b'"' => { 260 | return Ok((input.advance(offset), ())); 261 | } 262 | b'\r' => { 263 | if let Some((_, b'\n')) = bytes.next() { 264 | // ... 265 | } else { 266 | break; 267 | } 268 | } 269 | b'\\' => match bytes.next() { 270 | Some((_, b'x')) => { 271 | if !backslash_x_byte(&mut bytes) { 272 | break; 273 | } 274 | } 275 | Some((_, b'n')) | Some((_, b'r')) | Some((_, b't')) | Some((_, b'\\')) 276 | | Some((_, b'0')) | Some((_, b'\'')) | Some((_, b'"')) => {} 277 | Some((newline, b'\n')) | Some((newline, b'\r')) => { 278 | let rest = input.advance(newline + 1); 279 | for (offset, ch) in rest.char_indices() { 280 | if !ch.is_whitespace() { 281 | input = rest.advance(offset); 282 | bytes = input.bytes().enumerate(); 283 | continue 'outer; 284 | } 285 | } 286 | break; 287 | } 288 | _ => break, 289 | }, 290 | b if b < 0x80 => {} 291 | _ => break, 292 | } 293 | } 294 | Err(LexError { line: input.line }) 295 | } 296 | 297 | pub fn raw_string(input: Cursor) -> PResult<()> { 298 | let mut chars = input.char_indices(); 299 | let mut n = 0; 300 | #[allow(clippy::while_let_on_iterator)] //chars is used in the next loop 301 | while let Some((byte_offset, ch)) = chars.next() { 302 | match ch { 303 | '"' => { 304 | n = byte_offset; 305 | break; 306 | } 307 | '#' => {} 308 | _ => return Err(LexError { line: input.line }), 309 | } 310 | } 311 | for (byte_offset, ch) in chars { 312 | match ch { 313 | '"' if input.advance(byte_offset + 1).starts_with(&input.rest[..n]) => { 314 | let rest = input.advance(byte_offset + 1 + n); 315 | return Ok((rest, ())); 316 | } 317 | '\r' => {} 318 | _ => {} 319 | } 320 | } 321 | Err(LexError { line: input.line }) 322 | } 323 | 324 | pub fn cooked_byte(input: Cursor) -> PResult<()> { 325 | let mut bytes = input.bytes().enumerate(); 326 | let ok = match bytes.next().map(|(_, b)| b) { 327 | Some(b'\\') => match bytes.next().map(|(_, b)| b) { 328 | Some(b'x') => backslash_x_byte(&mut bytes), 329 | Some(b'n') | Some(b'r') | Some(b't') | Some(b'\\') | Some(b'0') | Some(b'\'') 330 | | Some(b'"') => true, 331 | _ => false, 332 | }, 333 | b => b.is_some(), 334 | }; 335 | if ok { 336 | match bytes.next() { 337 | Some((offset, _)) => { 338 | if input.chars().as_str().is_char_boundary(offset) { 339 | Ok((input.advance(offset), ())) 340 | } else { 341 | Err(LexError { line: input.line }) 342 | } 343 | } 344 | None => Ok((input.advance(input.len()), ())), 345 | } 346 | } else { 347 | Err(LexError { line: input.line }) 348 | } 349 | } 350 | 351 | pub fn cooked_char(input: Cursor) -> PResult<()> { 352 | let mut chars = input.char_indices(); 353 | let ok = match chars.next().map(|(_, ch)| ch) { 354 | Some('\\') => match chars.next().map(|(_, ch)| ch) { 355 | Some('x') => backslash_x_char(&mut chars), 356 | Some('u') => backslash_u(&mut chars), 357 | Some('n') | Some('r') | Some('t') | Some('\\') | Some('0') | Some('\'') | Some('"') => { 358 | true 359 | } 360 | _ => false, 361 | }, 362 | ch => ch.is_some(), 363 | }; 364 | if ok { 365 | match chars.next() { 366 | Some((idx, _)) => Ok((input.advance(idx), ())), 367 | None => Ok((input.advance(input.len()), ())), 368 | } 369 | } else { 370 | Err(LexError { line: input.line }) 371 | } 372 | } 373 | 374 | macro_rules! next_ch { 375 | ($chars:ident @ $pat:pat $(| $rest:pat)*) => { 376 | match $chars.next() { 377 | Some((_, ch)) => match ch { 378 | $pat $(| $rest)* => ch, 379 | _ => return false, 380 | }, 381 | None => return false 382 | } 383 | }; 384 | } 385 | 386 | fn backslash_x_char(chars: &mut I) -> bool 387 | where 388 | I: Iterator, 389 | { 390 | next_ch!(chars @ '0'..='7'); 391 | next_ch!(chars @ '0'..='9' | 'a'..='f' | 'A'..='F'); 392 | true 393 | } 394 | 395 | fn backslash_x_byte(chars: &mut I) -> bool 396 | where 397 | I: Iterator, 398 | { 399 | next_ch!(chars @ b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F'); 400 | next_ch!(chars @ b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F'); 401 | true 402 | } 403 | 404 | fn backslash_u(chars: &mut I) -> bool 405 | where 406 | I: Iterator, 407 | { 408 | next_ch!(chars @ '{'); 409 | next_ch!(chars @ '0'..='9' | 'a'..='f' | 'A'..='F'); 410 | loop { 411 | let c = next_ch!(chars @ '0'..='9' | 'a'..='f' | 'A'..='F' | '_' | '}'); 412 | if c == '}' { 413 | return true; 414 | } 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /cpp_common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp_common" 3 | version = "0.5.10" 4 | authors = ["Nika Layzell ", "Olivier Goffart "] 5 | edition = "2018" 6 | description = "Implementation details crate for the `cpp` crate" 7 | readme = "../README.md" 8 | license = "MIT/Apache-2.0" 9 | keywords = ["c", "cxx", "ffi", "compiler", "build-dependencies"] 10 | categories = ["development-tools::ffi"] 11 | repository = "https://github.com/mystor/rust-cpp" 12 | documentation = "https://docs.rs/cpp_common" 13 | 14 | [dependencies] 15 | syn = { version = "2.0", features = ["full", "extra-traits"] } 16 | lazy_static = "1.0" 17 | proc-macro2 = "1.0" 18 | -------------------------------------------------------------------------------- /cpp_common/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation detail for the `cpp` crate. 2 | //! 3 | //! The purpose of this crate is only to allow sharing code between the 4 | //! `cpp_build` and the `cpp_macros` crates. 5 | 6 | #[macro_use] 7 | extern crate syn; 8 | extern crate proc_macro2; 9 | 10 | #[macro_use] 11 | extern crate lazy_static; 12 | 13 | use std::collections::hash_map::DefaultHasher; 14 | use std::env; 15 | use std::hash::{Hash, Hasher}; 16 | use std::path::PathBuf; 17 | 18 | use proc_macro2::{Span, TokenStream, TokenTree}; 19 | use syn::ext::IdentExt; 20 | use syn::parse::{Parse, ParseStream, Result}; 21 | use syn::{Attribute, Ident, Type}; 22 | 23 | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 24 | 25 | pub const LIB_NAME: &str = "librust_cpp_generated.a"; 26 | pub const MSVC_LIB_NAME: &str = "rust_cpp_generated.lib"; 27 | 28 | pub mod flags { 29 | pub const IS_COPY_CONSTRUCTIBLE: u32 = 0; 30 | pub const IS_DEFAULT_CONSTRUCTIBLE: u32 = 1; 31 | pub const IS_TRIVIALLY_DESTRUCTIBLE: u32 = 2; 32 | pub const IS_TRIVIALLY_COPYABLE: u32 = 3; 33 | pub const IS_TRIVIALLY_DEFAULT_CONSTRUCTIBLE: u32 = 4; 34 | } 35 | 36 | pub mod kw { 37 | #![allow(non_camel_case_types)] 38 | custom_keyword!(rust); 39 | } 40 | 41 | /// This constant is expected to be a unique string within the compiled binary 42 | /// which precedes a definition of the metadata. It begins with 43 | /// rustcpp~metadata, which is printable to make it easier to locate when 44 | /// looking at a binary dump of the metadata. 45 | /// 46 | /// NOTE: In the future we may want to use a object file parser and a custom 47 | /// section rather than depending on this string being unique. 48 | #[rustfmt::skip] 49 | pub const STRUCT_METADATA_MAGIC: [u8; 128] = [ 50 | b'r', b'u', b's', b't', b'c', b'p', b'p', b'~', 51 | b'm', b'e', b't', b'a', b'd', b'a', b't', b'a', 52 | 92, 74, 112, 213, 165, 185, 214, 120, 179, 17, 185, 25, 182, 253, 82, 118, 53 | 148, 29, 139, 208, 59, 153, 78, 137, 230, 54, 26, 177, 232, 121, 132, 166, 54 | 44, 106, 218, 57, 158, 33, 69, 32, 54, 204, 123, 226, 99, 117, 60, 173, 55 | 112, 61, 56, 174, 117, 141, 126, 249, 79, 159, 6, 119, 2, 129, 147, 66, 56 | 135, 136, 212, 252, 231, 105, 239, 91, 96, 232, 113, 94, 164, 255, 152, 144, 57 | 64, 207, 192, 90, 225, 171, 59, 154, 60, 2, 0, 191, 114, 182, 38, 134, 58 | 134, 183, 212, 227, 31, 217, 12, 5, 65, 221, 150, 59, 230, 96, 73, 62, 59 | ]; 60 | 61 | lazy_static! { 62 | pub static ref OUT_DIR: PathBuf = PathBuf::from(env::var("OUT_DIR").expect( 63 | r#" 64 | -- rust-cpp fatal error -- 65 | 66 | The OUT_DIR environment variable was not set. 67 | NOTE: rustc must be run by Cargo."# 68 | )); 69 | pub static ref FILE_HASH: u64 = { 70 | let mut hasher = std::collections::hash_map::DefaultHasher::new(); 71 | OUT_DIR.hash(&mut hasher); 72 | hasher.finish() 73 | }; 74 | } 75 | 76 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 77 | pub struct Capture { 78 | pub mutable: bool, 79 | pub name: Ident, 80 | pub cpp: String, 81 | } 82 | 83 | impl Parse for Capture { 84 | /// Parse a single captured variable inside within a `cpp!` macro. 85 | /// Example: `mut foo as "int"` 86 | fn parse(input: ParseStream) -> Result { 87 | Ok(Capture { 88 | mutable: input.parse::>()?.is_some(), 89 | name: input.call(Ident::parse_any)?, 90 | cpp: { 91 | input.parse::()?; 92 | input.parse::()?.value() 93 | }, 94 | }) 95 | } 96 | } 97 | 98 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 99 | pub struct ClosureSig { 100 | pub captures: Vec, 101 | pub ret: Option, 102 | pub cpp: String, 103 | pub std_body: String, 104 | } 105 | 106 | impl ClosureSig { 107 | pub fn name_hash(&self) -> u64 { 108 | // XXX: Use a better hasher than the default? 109 | let mut hasher = DefaultHasher::new(); 110 | self.hash(&mut hasher); 111 | hasher.finish() 112 | } 113 | 114 | pub fn extern_name(&self) -> Ident { 115 | Ident::new(&format!("__cpp_closure_{}", self.name_hash()), Span::call_site()) 116 | } 117 | } 118 | 119 | #[derive(Clone, Debug)] 120 | pub struct Closure { 121 | pub sig: ClosureSig, 122 | pub body: TokenTree, 123 | pub body_str: String, // with `rust!` macro replaced 124 | pub callback_offset: u32, 125 | } 126 | 127 | impl Parse for Closure { 128 | /// Parse the inside of a `cpp!` macro when this macro is a closure. 129 | /// Example: `unsafe [foo as "int"] -> u32 as "int" { /*... */ } 130 | fn parse(input: ParseStream) -> Result { 131 | input.parse::>()?; 132 | 133 | // Capture 134 | let capture_content; 135 | bracketed!(capture_content in input); 136 | let captures = 137 | syn::punctuated::Punctuated::::parse_terminated(&capture_content)? 138 | .into_iter() 139 | .collect(); 140 | 141 | // Optional return type 142 | let (ret, cpp) = if input.peek(Token![->]) { 143 | input.parse::]>()?; 144 | let t: syn::Type = input.parse()?; 145 | input.parse::()?; 146 | let s = input.parse::()?.value(); 147 | (Some(t), s) 148 | } else { 149 | (None, "void".to_owned()) 150 | }; 151 | 152 | let body = input.parse::()?; 153 | // Need to filter the spaces because there is a difference between 154 | // proc_macro2 and proc_macro and the hashes would not match 155 | let std_body = body.to_string().chars().filter(|x| !x.is_whitespace()).collect(); 156 | 157 | Ok(Closure { 158 | sig: ClosureSig { captures, ret, cpp, std_body }, 159 | body, 160 | body_str: String::new(), 161 | callback_offset: 0, 162 | }) 163 | } 164 | } 165 | 166 | #[derive(Clone, Debug)] 167 | pub struct Class { 168 | pub name: Ident, 169 | pub cpp: String, 170 | pub attrs: Vec, 171 | pub line: String, // the #line directive 172 | } 173 | 174 | impl Class { 175 | pub fn name_hash(&self) -> u64 { 176 | let mut hasher = DefaultHasher::new(); 177 | self.name.hash(&mut hasher); 178 | self.cpp.hash(&mut hasher); 179 | hasher.finish() 180 | } 181 | 182 | pub fn derives(&self, i: &str) -> bool { 183 | self.attrs.iter().any(|x| { 184 | let mut result = false; 185 | if x.path().is_ident("derive") { 186 | x.parse_nested_meta(|m| { 187 | if m.path.is_ident(i) { 188 | result = true; 189 | } 190 | Ok(()) 191 | }) 192 | .unwrap(); 193 | } 194 | result 195 | }) 196 | } 197 | } 198 | 199 | impl Parse for Class { 200 | /// Parse the inside of a `cpp_class!` macro. 201 | /// Example: `#[derive(Default)] pub unsafe struct Foobar as "FooBar"` 202 | fn parse(input: ParseStream) -> Result { 203 | Ok(Class { 204 | attrs: input.call(Attribute::parse_outer)?, 205 | name: { 206 | input.parse::()?; 207 | input.parse::()?; 208 | input.parse::()?; 209 | input.parse()? 210 | }, 211 | cpp: { 212 | input.parse::()?; 213 | input.parse::()?.value() 214 | }, 215 | line: String::new(), 216 | }) 217 | } 218 | } 219 | 220 | #[allow(clippy::large_enum_variant)] 221 | #[derive(Debug)] 222 | pub enum Macro { 223 | Closure(Closure), 224 | Lit(TokenStream), 225 | } 226 | 227 | impl Parse for Macro { 228 | /// Parse the inside of a `cpp!` macro (a literal or a closure) 229 | fn parse(input: ParseStream) -> Result { 230 | if input.peek(syn::token::Brace) { 231 | let content; 232 | braced!(content in input); 233 | return Ok(Macro::Lit(content.parse()?)); 234 | } 235 | Ok(Macro::Closure(input.parse::()?)) 236 | } 237 | } 238 | 239 | #[derive(Debug)] 240 | pub struct RustInvocation { 241 | pub begin: Span, 242 | pub end: Span, 243 | pub id: Ident, 244 | pub return_type: Option, 245 | pub arguments: Vec<(Ident, String)>, // Vec of name and type 246 | } 247 | 248 | impl Parse for RustInvocation { 249 | /// Parse a `rust!` macro something looking like `rust!(ident [foo : bar as "bar"] { /*...*/ })` 250 | fn parse(input: ParseStream) -> Result { 251 | let rust_token = input.parse::()?; 252 | input.parse::()?; 253 | let macro_content; 254 | let p = parenthesized!(macro_content in input); 255 | let r = RustInvocation { 256 | begin: rust_token.span, 257 | end: p.span.close(), 258 | id: macro_content.parse()?, 259 | arguments: { 260 | let capture_content; 261 | bracketed!(capture_content in macro_content); 262 | capture_content 263 | .parse_terminated( 264 | |input: ParseStream| -> Result<(Ident, String)> { 265 | let i = input.call(Ident::parse_any)?; 266 | input.parse::()?; 267 | input.parse::()?; 268 | input.parse::()?; 269 | let s = input.parse::()?.value(); 270 | Ok((i, s)) 271 | }, 272 | Token![,], 273 | )? 274 | .into_iter() 275 | .collect() 276 | }, 277 | return_type: if macro_content.peek(Token![->]) { 278 | macro_content.parse::]>()?; 279 | macro_content.parse::()?; 280 | macro_content.parse::()?; 281 | Some(macro_content.parse::()?.value()) 282 | } else { 283 | None 284 | }, 285 | }; 286 | macro_content.parse::()?; 287 | Ok(r) 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /cpp_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp_macros" 3 | version = "0.5.10" 4 | authors = ["Nika Layzell ", "Olivier Goffart "] 5 | edition = "2018" 6 | description = "Procedural macro implementation for the `cpp` crate" 7 | readme = "../README.md" 8 | license = "MIT/Apache-2.0" 9 | keywords = ["c", "cxx", "ffi", "compiler"] 10 | categories = ["development-tools::ffi"] 11 | repository = "https://github.com/mystor/rust-cpp" 12 | documentation = "https://docs.rs/cpp_macros" 13 | 14 | [features] 15 | # Ignore compilations error of the C++ code when building the documentation, as the docs.rs server 16 | # might not have the required libraries 17 | docs-only = [] 18 | 19 | [lib] 20 | proc-macro = true 21 | 22 | [dependencies] 23 | lazy_static = "1.0" 24 | cpp_common = { path = "../cpp_common", version = "=0.5.10" } 25 | syn = { version = "2.0", features=["full", "visit"] } 26 | quote = "1.0" 27 | proc-macro2 = "1.0" 28 | aho-corasick = "1.0" 29 | byteorder = "1.0" 30 | 31 | [package.metadata.docs.rs] 32 | features = [ "docs-only" ] 33 | -------------------------------------------------------------------------------- /cpp_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate is the `cpp` procedural macro implementation. It is useless 2 | //! without the companion crates `cpp`, and `cpp_build`. 3 | //! 4 | //! For more information, see the [`cpp` crate module level 5 | //! documentation](https://docs.rs/cpp). 6 | #![recursion_limit = "128"] 7 | 8 | #[macro_use] 9 | extern crate syn; 10 | extern crate proc_macro; 11 | use proc_macro2::Span; 12 | 13 | use cpp_common::{flags, kw, RustInvocation, FILE_HASH, LIB_NAME, MSVC_LIB_NAME, OUT_DIR, VERSION}; 14 | use std::collections::HashMap; 15 | use std::iter::FromIterator; 16 | use syn::parse::Parser; 17 | use syn::Ident; 18 | 19 | use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt}; 20 | use lazy_static::lazy_static; 21 | use quote::{quote, quote_spanned}; 22 | use std::fs::File; 23 | use std::io::{self, BufReader, Read, Seek, SeekFrom}; 24 | 25 | struct MetaData { 26 | size: usize, 27 | align: usize, 28 | flags: u64, 29 | } 30 | impl MetaData { 31 | fn has_flag(&self, f: u32) -> bool { 32 | self.flags & (1 << f) != 0 33 | } 34 | } 35 | 36 | lazy_static! { 37 | static ref METADATA: HashMap> = { 38 | let file = match open_lib_file() { 39 | Ok(x) => x, 40 | Err(e) => { 41 | #[cfg(not(feature = "docs-only"))] 42 | panic!( 43 | r#" 44 | -- rust-cpp fatal error -- 45 | 46 | Failed to open the target library file. 47 | NOTE: Did you make sure to add the rust-cpp build script? 48 | {}"#, 49 | e 50 | ); 51 | #[cfg(feature = "docs-only")] 52 | { 53 | eprintln!("Error while opening target library: {}", e); 54 | return Default::default(); 55 | }; 56 | } 57 | }; 58 | 59 | read_metadata(file).expect( 60 | r#" 61 | -- rust-cpp fatal error -- 62 | 63 | I/O error while reading metadata from target library file."#, 64 | ) 65 | }; 66 | } 67 | 68 | /// NOTE: This panics when it can produce a better error message 69 | fn read_metadata(file: File) -> io::Result>> { 70 | let mut file = BufReader::new(file); 71 | let end = { 72 | const AUTO_KEYWORD: &[&[u8]] = &[&cpp_common::STRUCT_METADATA_MAGIC]; 73 | let aut = aho_corasick::AhoCorasick::new(AUTO_KEYWORD).unwrap(); 74 | let found = aut.stream_find_iter(&mut file).next().expect( 75 | r#" 76 | -- rust-cpp fatal error -- 77 | 78 | Struct metadata not present in target library file. 79 | NOTE: Double-check that the version of cpp_build and cpp_macros match"#, 80 | )?; 81 | found.end() 82 | }; 83 | file.seek(SeekFrom::Start(end as u64))?; 84 | 85 | // Read & convert the version buffer into a string & compare with our 86 | // version. 87 | let mut version_buf = [0; 16]; 88 | file.read_exact(&mut version_buf)?; 89 | let version = 90 | version_buf.iter().take_while(|b| **b != b'\0').map(|b| *b as char).collect::(); 91 | 92 | assert_eq!( 93 | version, VERSION, 94 | r#" 95 | -- rust-cpp fatal error -- 96 | 97 | Version mismatch between cpp_macros and cpp_build for same crate."# 98 | ); 99 | let endianness_check = file.read_u64::()?; 100 | if endianness_check == 0xffef { 101 | read_metadata_rest::(file) 102 | } else if endianness_check == 0xefff000000000000 { 103 | read_metadata_rest::(file) 104 | } else { 105 | panic!("Endianness check value matches neither little nor big endian."); 106 | } 107 | } 108 | 109 | fn read_metadata_rest( 110 | mut file: BufReader, 111 | ) -> io::Result>> { 112 | let length = file.read_u64::()?; 113 | let mut metadata = HashMap::new(); 114 | for _ in 0..length { 115 | let hash = file.read_u64::()?; 116 | let size = file.read_u64::()? as usize; 117 | let align = file.read_u64::()? as usize; 118 | let flags = file.read_u64::()?; 119 | 120 | metadata.entry(hash).or_insert_with(Vec::new).push(MetaData { size, align, flags }); 121 | } 122 | Ok(metadata) 123 | } 124 | 125 | /// Try to open a file handle to the lib file. This is used to scan it for 126 | /// metadata. We check both `MSVC_LIB_NAME` and `LIB_NAME`, in case we are on 127 | /// or are targeting Windows. 128 | fn open_lib_file() -> io::Result { 129 | if let Ok(file) = File::open(OUT_DIR.join(MSVC_LIB_NAME)) { 130 | Ok(file) 131 | } else { 132 | File::open(OUT_DIR.join(LIB_NAME)) 133 | } 134 | } 135 | 136 | fn find_all_rust_macro( 137 | input: syn::parse::ParseStream, 138 | ) -> Result, syn::parse::Error> { 139 | let mut r = Vec::::new(); 140 | while !input.is_empty() { 141 | if input.peek(kw::rust) { 142 | if let Ok(ri) = input.parse::() { 143 | r.push(ri); 144 | } 145 | } else if input.peek(syn::token::Brace) { 146 | let c; 147 | braced!(c in input); 148 | r.extend(find_all_rust_macro(&c)?); 149 | } else if input.peek(syn::token::Paren) { 150 | let c; 151 | parenthesized!(c in input); 152 | r.extend(find_all_rust_macro(&c)?); 153 | } else if input.peek(syn::token::Bracket) { 154 | let c; 155 | bracketed!(c in input); 156 | r.extend(find_all_rust_macro(&c)?); 157 | } else { 158 | input.parse::()?; 159 | } 160 | } 161 | Ok(r) 162 | } 163 | 164 | /// Find the occurrence of the `stringify!` macro within the macro derive 165 | fn extract_original_macro(input: &syn::DeriveInput) -> Option { 166 | #[derive(Default)] 167 | struct Finder(Option); 168 | impl<'ast> syn::visit::Visit<'ast> for Finder { 169 | fn visit_macro(&mut self, mac: &'ast syn::Macro) { 170 | if mac.path.segments.len() == 1 && mac.path.segments[0].ident == "stringify" { 171 | self.0 = Some(mac.tokens.clone()); 172 | } 173 | } 174 | } 175 | let mut f = Finder::default(); 176 | syn::visit::visit_derive_input(&mut f, input); 177 | f.0 178 | } 179 | 180 | #[proc_macro_derive(__cpp_internal_closure)] 181 | #[allow(clippy::cognitive_complexity)] 182 | pub fn expand_internal(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 183 | assert_eq!( 184 | env!("CARGO_PKG_VERSION"), 185 | VERSION, 186 | "Internal Error: mismatched cpp_common and cpp_macros versions" 187 | ); 188 | 189 | // Parse the macro input 190 | let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap(); 191 | 192 | let closure = match syn::parse2::(input) { 193 | Ok(x) => x, 194 | Err(err) => return err.to_compile_error().into(), 195 | }; 196 | 197 | // Get the size data compiled by the build macro 198 | let size_data = match METADATA.get(&closure.sig.name_hash()) { 199 | Some(x) => x, 200 | None => { 201 | #[cfg(not(feature = "docs-only"))] 202 | return quote!(compile_error! { 203 | r#"This cpp! macro is not found in the library's rust-cpp metadata. 204 | NOTE: Only cpp! macros found directly in the program source will be parsed - 205 | NOTE: They cannot be generated by macro expansion."#}) 206 | .into(); 207 | #[cfg(feature = "docs-only")] 208 | { 209 | return quote! { 210 | macro_rules! __cpp_closure_impl { 211 | ($($x:tt)*) => { panic!("docs-only"); } 212 | } 213 | } 214 | .into(); 215 | }; 216 | } 217 | }; 218 | 219 | let mut extern_params = Vec::new(); 220 | let mut tt_args = Vec::new(); 221 | let mut call_args = Vec::new(); 222 | for (i, capture) in closure.sig.captures.iter().enumerate() { 223 | let written_name = &capture.name; 224 | let span = written_name.span(); 225 | let mac_name = Ident::new(&format!("var_{}", written_name), span); 226 | let mac_cty = Ident::new(&format!("cty_{}", written_name), span); 227 | 228 | // Generate the assertion to check that the size and align of the types 229 | // match before calling. 230 | let MetaData { size, align, .. } = size_data[i + 1]; 231 | let sizeof_msg = format!( 232 | "size_of for argument `{}` does not match between c++ and \ 233 | rust", 234 | &capture.name 235 | ); 236 | let alignof_msg = format!( 237 | "align_of for argument `{}` does not match between c++ and \ 238 | rust", 239 | &capture.name 240 | ); 241 | let assertion = quote_spanned! {span=> 242 | // Perform a compile time check that the sizes match. This should be 243 | // a no-op. 244 | if false { 245 | #[allow(clippy::transmute_num_to_bytes)] 246 | ::core::mem::transmute::<_, [u8; #size]>( 247 | ::core::ptr::read(&$#mac_name)); 248 | } 249 | 250 | // NOTE: Both of these calls should be dead code in opt builds. 251 | #[allow(clippy::size_of_ref)] { assert!(::core::mem::size_of_val(&$#mac_name) == #size, #sizeof_msg); }; 252 | assert!(::core::mem::align_of_val(&$#mac_name) == #align, 253 | #alignof_msg); 254 | }; 255 | 256 | let mb_mut = if capture.mutable { quote_spanned!(span=> mut) } else { quote!() }; 257 | let ptr = if capture.mutable { 258 | quote_spanned!(span=> *mut) 259 | } else { 260 | quote_spanned!(span=> *const) 261 | }; 262 | 263 | let arg_name = Ident::new(&format!("arg_{}", written_name), span); 264 | 265 | extern_params.push(quote_spanned!(span=> #arg_name : #ptr u8)); 266 | 267 | tt_args.push(quote_spanned!(span=> #mb_mut $#mac_name : ident as $#mac_cty : tt)); 268 | 269 | call_args.push(quote_spanned!(span=> { 270 | #assertion 271 | &#mb_mut $#mac_name as #ptr _ as #ptr u8 272 | })); 273 | } 274 | 275 | let extern_name = closure.sig.extern_name(); 276 | let ret_ty = &closure.sig.ret; 277 | let MetaData { size: ret_size, align: ret_align, flags } = size_data[0]; 278 | let is_void = closure.sig.cpp == "void"; 279 | 280 | let decl = if is_void { 281 | quote! { 282 | fn #extern_name(#(#extern_params),*); 283 | } 284 | } else { 285 | quote! { 286 | fn #extern_name(#(#extern_params,)* _result: *mut #ret_ty); 287 | } 288 | }; 289 | 290 | let call = if is_void { 291 | assert!(ret_size == 0, "`void` should have a size of 0!"); 292 | quote! { 293 | #extern_name(#(#call_args),*); 294 | #[allow(clippy::useless_transmute)] 295 | ::core::mem::transmute::<(), (#ret_ty)>(()) 296 | } 297 | } else { 298 | // static assert that the size and alignement are the same 299 | let assert_size = quote! { 300 | if false { 301 | const _assert_size: [(); #ret_size] = [(); ::core::mem::size_of::<#ret_ty>()]; 302 | const _assert_align: [(); #ret_align] = [(); ::core::mem::align_of::<#ret_ty>()]; 303 | } 304 | }; 305 | quote!( 306 | #assert_size 307 | let mut result = ::core::mem::MaybeUninit::<#ret_ty>::uninit(); 308 | #extern_name(#(#call_args,)* result.as_mut_ptr()); 309 | result.assume_init() 310 | ) 311 | }; 312 | 313 | let input = proc_macro2::TokenStream::from_iter([closure.body].iter().cloned()); 314 | let rust_invocations = find_all_rust_macro.parse2(input).expect("rust! macro"); 315 | let init_callbacks = if !rust_invocations.is_empty() { 316 | let rust_cpp_callbacks = 317 | Ident::new(&format!("rust_cpp_callbacks{}", *FILE_HASH), Span::call_site()); 318 | let offset = (flags >> 32) as isize; 319 | let callbacks: Vec = rust_invocations.iter().map(|x| x.id.clone()).collect(); 320 | quote! { 321 | use ::std::sync::Once; 322 | static INIT_INVOCATIONS: Once = Once::new(); 323 | INIT_INVOCATIONS.call_once(|| { 324 | // #rust_cpp_callbacks is in fact an array. Since we cannot represent it in rust, 325 | // we just are gonna take the pointer to it can offset from that. 326 | extern "C" { 327 | #[no_mangle] 328 | static mut #rust_cpp_callbacks: *const ::std::os::raw::c_void; 329 | } 330 | let callbacks_array : *mut *const ::std::os::raw::c_void = &mut #rust_cpp_callbacks; 331 | let mut offset = #offset; 332 | #( 333 | offset += 1; 334 | *callbacks_array.offset(offset - 1) = #callbacks as *const ::std::os::raw::c_void; 335 | )* 336 | }); 337 | } 338 | } else { 339 | quote!() 340 | }; 341 | 342 | let result = quote! { 343 | extern "C" { 344 | #decl 345 | } 346 | 347 | macro_rules! __cpp_closure_impl { 348 | (#(#tt_args),*) => { 349 | { 350 | #init_callbacks 351 | #call 352 | } 353 | } 354 | } 355 | }; 356 | result.into() 357 | } 358 | 359 | #[proc_macro_derive(__cpp_internal_class)] 360 | pub fn expand_wrap_class(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 361 | // Parse the macro input 362 | let input = extract_original_macro(&parse_macro_input!(input as syn::DeriveInput)).unwrap(); 363 | 364 | let class = match ::syn::parse2::(input) { 365 | Ok(x) => x, 366 | Err(err) => return err.to_compile_error().into(), 367 | }; 368 | 369 | let hash = class.name_hash(); 370 | let class_name = class.name.clone(); 371 | 372 | // Get the size data compiled by the build macro 373 | let size_data = match METADATA.get(&hash) { 374 | Some(x) => x, 375 | None => { 376 | #[cfg(not(feature = "docs-only"))] 377 | return quote!(compile_error! { 378 | r#"This cpp_class! macro is not found in the library's rust-cpp metadata. 379 | NOTE: Only cpp_class! macros found directly in the program source will be parsed - 380 | NOTE: They cannot be generated by macro expansion."#}) 381 | .into(); 382 | #[cfg(feature = "docs-only")] 383 | { 384 | let mut result = quote! { 385 | #[doc(hidden)] 386 | impl ::cpp::CppTrait for #class_name { 387 | type BaseType = usize; 388 | const ARRAY_SIZE: usize = 1; 389 | const CPP_TYPE: &'static str = stringify!(#class_name); 390 | } 391 | #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is trivially copyable"] 392 | impl ::core::marker::Copy for #class_name { } 393 | #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is copyable"] 394 | impl ::core::clone::Clone for #class_name { fn clone(&self) -> Self { panic!("docs-only") } } 395 | #[doc = "NOTE: this trait will only be enabled if the C++ underlying type is default constructible"] 396 | impl ::core::default::Default for #class_name { fn default() -> Self { panic!("docs-only") } } 397 | }; 398 | if class.derives("PartialEq") { 399 | result = quote! { #result 400 | impl ::core::cmp::PartialEq for #class_name { 401 | fn eq(&self, other: &#class_name) -> bool { panic!("docs-only") } 402 | } 403 | }; 404 | } 405 | if class.derives("PartialOrd") { 406 | result = quote! { #result 407 | impl ::core::cmp::PartialOrd for #class_name { 408 | fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> { 409 | panic!("docs-only") 410 | } 411 | } 412 | }; 413 | } 414 | if class.derives("Ord") { 415 | result = quote! { #result 416 | impl ::core::cmp::Ord for #class_name { 417 | fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering { 418 | panic!("docs-only") 419 | } 420 | } 421 | }; 422 | } 423 | return result.into(); 424 | }; 425 | } 426 | }; 427 | 428 | let (size, align) = (size_data[0].size, size_data[0].align); 429 | 430 | let base_type = match align { 431 | 1 => quote!(u8), 432 | 2 => quote!(u16), 433 | 4 => quote!(u32), 434 | 8 => quote!(u64), 435 | _ => panic!("unsupported alignment"), 436 | }; 437 | 438 | let destructor_name = Ident::new(&format!("__cpp_destructor_{}", hash), Span::call_site()); 439 | let copyctr_name = Ident::new(&format!("__cpp_copy_{}", hash), Span::call_site()); 440 | let defaultctr_name = Ident::new(&format!("__cpp_default_{}", hash), Span::call_site()); 441 | 442 | let mut result = quote! { 443 | #[doc(hidden)] 444 | impl ::cpp::CppTrait for #class_name { 445 | type BaseType = #base_type; 446 | const ARRAY_SIZE: usize = #size / #align; 447 | const CPP_TYPE: &'static str = stringify!(#class_name); 448 | } 449 | }; 450 | if !size_data[0].has_flag(flags::IS_TRIVIALLY_DESTRUCTIBLE) { 451 | result = quote! { #result 452 | impl ::core::ops::Drop for #class_name { 453 | fn drop(&mut self) { 454 | unsafe { 455 | extern "C" { fn #destructor_name(_: *mut #class_name); } 456 | #destructor_name(&mut *self); 457 | } 458 | } 459 | } 460 | }; 461 | }; 462 | 463 | if size_data[0].has_flag(flags::IS_COPY_CONSTRUCTIBLE) { 464 | if !size_data[0].has_flag(flags::IS_TRIVIALLY_COPYABLE) && !class.derives("Copy") { 465 | let call_construct = quote!( 466 | let mut result = ::core::mem::MaybeUninit::::uninit(); 467 | #copyctr_name(& *self, result.as_mut_ptr()); 468 | result.assume_init() 469 | ); 470 | result = quote! { #result 471 | impl ::core::clone::Clone for #class_name { 472 | fn clone(&self) -> Self { 473 | unsafe { 474 | extern "C" { fn #copyctr_name(src: *const #class_name, dst: *mut #class_name); } 475 | #call_construct 476 | } 477 | } 478 | } 479 | }; 480 | } else { 481 | result = quote! { #result 482 | impl ::core::marker::Copy for #class_name { } 483 | impl ::core::clone::Clone for #class_name { 484 | fn clone(&self) -> Self { *self } 485 | } 486 | }; 487 | }; 488 | } else if class.derives("Clone") { 489 | panic!("C++ class is not copyable"); 490 | } 491 | 492 | if size_data[0].has_flag(flags::IS_DEFAULT_CONSTRUCTIBLE) { 493 | let call_construct = quote!( 494 | let mut result = ::core::mem::MaybeUninit::::uninit(); 495 | #defaultctr_name(result.as_mut_ptr()); 496 | result.assume_init() 497 | ); 498 | result = quote! { #result 499 | impl ::core::default::Default for #class_name { 500 | fn default() -> Self { 501 | unsafe { 502 | extern "C" { fn #defaultctr_name(dst: *mut #class_name); } 503 | #call_construct 504 | } 505 | } 506 | } 507 | }; 508 | } else if class.derives("Default") { 509 | panic!("C++ class is not default constructible"); 510 | } 511 | 512 | if class.derives("PartialEq") { 513 | let equal_name = Ident::new(&format!("__cpp_equal_{}", hash), Span::call_site()); 514 | result = quote! { #result 515 | impl ::core::cmp::PartialEq for #class_name { 516 | fn eq(&self, other: &#class_name) -> bool { 517 | unsafe { 518 | extern "C" { fn #equal_name(a: *const #class_name, b: *const #class_name) -> bool; } 519 | #equal_name(& *self, other) 520 | } 521 | } 522 | } 523 | }; 524 | } 525 | if class.derives("PartialOrd") { 526 | let compare_name = Ident::new(&format!("__cpp_compare_{}", hash), Span::call_site()); 527 | let f = |func, cmp| { 528 | quote! { 529 | fn #func(&self, other: &#class_name) -> bool { 530 | unsafe { 531 | extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; } 532 | #compare_name(& *self, other, #cmp) != 0 533 | } 534 | } 535 | } 536 | }; 537 | let lt = f(quote! {lt}, -2); 538 | let gt = f(quote! {gt}, 2); 539 | let le = f(quote! {le}, -1); 540 | let ge = f(quote! {ge}, 1); 541 | result = quote! { #result 542 | impl ::core::cmp::PartialOrd for #class_name { 543 | #lt #gt #le #ge 544 | 545 | fn partial_cmp(&self, other: &#class_name) -> ::core::option::Option<::core::cmp::Ordering> { 546 | use ::core::cmp::Ordering; 547 | unsafe { 548 | extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; } 549 | ::core::option::Option::Some(match #compare_name(& *self, other, 0) { 550 | -1 => Ordering::Less, 551 | 0 => Ordering::Equal, 552 | 1 => Ordering::Greater, 553 | _ => panic!() 554 | }) 555 | } 556 | } 557 | } 558 | }; 559 | } 560 | if class.derives("Ord") { 561 | let compare_name = Ident::new(&format!("__cpp_compare_{}", hash), Span::call_site()); 562 | result = quote! { #result 563 | impl ::core::cmp::Ord for #class_name { 564 | fn cmp(&self, other: &#class_name) -> ::core::cmp::Ordering { 565 | unsafe { 566 | use ::core::cmp::Ordering; 567 | extern "C" { fn #compare_name(a: *const #class_name, b: *const #class_name, cmp : i32) -> i32; } 568 | match #compare_name(& *self, other, 0) { 569 | -1 => Ordering::Less, 570 | 0 => Ordering::Equal, 571 | 1 => Ordering::Greater, 572 | _ => panic!() 573 | } 574 | } 575 | } 576 | } 577 | }; 578 | } 579 | 580 | if class.derives("Hash") { 581 | panic!("Deriving from Hash is not implemented") 582 | }; 583 | if class.derives("Debug") { 584 | panic!("Deriving from Debug is not implemented") 585 | }; 586 | 587 | result.into() 588 | } 589 | -------------------------------------------------------------------------------- /publish_crates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo publish --manifest-path cpp_common/Cargo.toml 4 | cargo publish --manifest-path cpp_macros/Cargo.toml 5 | cargo publish --manifest-path cpp_build/Cargo.toml 6 | cargo publish --manifest-path cpp/Cargo.toml 7 | 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpp_test" 3 | version = "0.1.0" 4 | authors = ["Nika Layzell "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | [build-dependencies] 9 | cpp_build = { path = "../cpp_build" } 10 | 11 | [dependencies] 12 | cpp = { path = "../cpp" } 13 | -------------------------------------------------------------------------------- /test/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cpp_build; 2 | 3 | fn main() { 4 | cpp_build::build("src/lib.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /test/src/cpp_class.rs: -------------------------------------------------------------------------------- 1 | use super::A; 2 | use cpp::{cpp, cpp_class}; 3 | 4 | #[test] 5 | fn destructor() { 6 | unsafe { 7 | let a = cpp!([] -> A as "A" { 8 | return A(5, 10); 9 | }); 10 | 11 | let a1 = a.clone(); 12 | 13 | let first = cpp!([a as "A"] -> i32 as "int32_t" { 14 | return a.a; 15 | }); 16 | 17 | assert_eq!(first, 5); 18 | 19 | let second = cpp!([a1 as "A"] -> i32 as "int32_t" { 20 | return a1.b; 21 | }); 22 | 23 | assert_eq!(second, 10); 24 | } 25 | } 26 | 27 | #[test] 28 | fn member_function() { 29 | let mut a = A::new(2, 3); 30 | assert_eq!(a.multiply(), 2 * 3); 31 | 32 | a.set_values(5, 6); 33 | assert_eq!(a.multiply(), 5 * 6); 34 | } 35 | 36 | cpp_class!(pub(crate) unsafe struct B as "B"); 37 | impl B { 38 | fn new(a: i32, b: i32) -> Self { 39 | unsafe { 40 | return cpp!([a as "int", b as "int"] -> B as "B" { 41 | B ret = { a, b }; 42 | return ret; 43 | }); 44 | } 45 | } 46 | fn a(&mut self) -> &mut i32 { 47 | unsafe { 48 | return cpp!([self as "B*"] -> &mut i32 as "int*" { 49 | return &self->a; 50 | }); 51 | } 52 | } 53 | fn b(&mut self) -> &mut i32 { 54 | unsafe { 55 | return cpp!([self as "B*"] -> &mut i32 as "int*" { 56 | return &self->b; 57 | }); 58 | } 59 | } 60 | } 61 | 62 | #[test] 63 | fn simple_class() { 64 | let mut b = B::new(12, 34); 65 | assert_eq!(*b.a(), 12); 66 | assert_eq!(*b.b(), 34); 67 | *b.a() = 45; 68 | let mut b2 = b; 69 | assert_eq!(*b2.a(), 45); 70 | 71 | let mut b3 = B::default(); 72 | assert_eq!(*b3.a(), 0); 73 | assert_eq!(*b3.b(), 0); 74 | } 75 | 76 | #[test] 77 | fn move_only() { 78 | cpp_class!(unsafe struct MoveOnly as "MoveOnly"); 79 | impl MoveOnly { 80 | fn data(&self) -> &A { 81 | unsafe { 82 | return cpp!([self as "MoveOnly*"] -> &A as "A*" { 83 | return &self->data; 84 | }); 85 | } 86 | } 87 | } 88 | let mo1 = MoveOnly::default(); 89 | assert_eq!(mo1.data().multiply(), 8 * 9); 90 | let mut mo2 = mo1; 91 | let mo3 = unsafe { 92 | cpp!([mut mo2 as "MoveOnly"] -> MoveOnly as "MoveOnly" { 93 | mo2.data.a = 7; 94 | return MoveOnly(3,2); 95 | }) 96 | }; 97 | assert_eq!(mo2.data().multiply(), 7 * 9); 98 | assert_eq!(mo3.data().multiply(), 3 * 2); 99 | } 100 | 101 | #[test] 102 | fn derive_eq() { 103 | cpp! {{ 104 | struct WithOpEq { 105 | static int val; 106 | int value = val++; 107 | friend bool operator==(const WithOpEq &a, const WithOpEq &b) { return a.value == b.value; } 108 | }; 109 | int WithOpEq::val = 0; 110 | }}; 111 | cpp_class!(#[derive(Eq, PartialEq)] unsafe struct WithOpEq as "WithOpEq"); 112 | 113 | let x1 = WithOpEq::default(); 114 | let x2 = WithOpEq::default(); 115 | 116 | assert!(!(x1 == x2)); 117 | assert!(x1 != x2); 118 | 119 | let x3 = x1.clone(); 120 | assert!(x1 == x3); 121 | assert!(!(x1 != x3)); 122 | } 123 | 124 | #[test] 125 | fn derive_ord() { 126 | cpp! {{ 127 | struct Comp { 128 | int value; 129 | Comp(int i) : value(i) { } 130 | friend bool operator<(const Comp &a, const Comp &b) { return a.value < b.value; } 131 | friend bool operator==(const Comp &a, const Comp &b) { return a.value == b.value; } 132 | }; 133 | }}; 134 | cpp_class!(#[derive(PartialEq, PartialOrd)] #[derive(Eq, Ord)] unsafe struct Comp as "Comp"); 135 | impl Comp { 136 | fn new(i: u32) -> Comp { 137 | unsafe { cpp!([i as "int"] -> Comp as "Comp" { return i; }) } 138 | } 139 | } 140 | 141 | let x1 = Comp::new(1); 142 | let x2 = Comp::new(2); 143 | let x3 = Comp::new(3); 144 | assert!(x1 < x2); 145 | assert!(x2 > x1); 146 | assert!(x3 > x1); 147 | assert!(x3 >= x1); 148 | assert!(x3 >= x3); 149 | assert!(x2 <= x3); 150 | assert!(x2 <= x2); 151 | assert!(!(x1 > x2)); 152 | assert!(!(x2 < x1)); 153 | assert!(!(x3 <= x1)); 154 | assert!(!(x1 < x1)); 155 | assert!(!(x3 > x3)); 156 | assert!(!(x3 < x3)); 157 | assert!(!(x2 >= x3)); 158 | } 159 | -------------------------------------------------------------------------------- /test/src/examples.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | cpp! {{ 4 | #include 5 | }} 6 | 7 | #[test] 8 | fn main() { 9 | let name = std::ffi::CString::new("World").unwrap(); 10 | let name_ptr = name.as_ptr(); 11 | let r = unsafe { 12 | cpp!([name_ptr as "const char *"] -> u32 as "int32_t" { 13 | std::cout << "Hello, " << name_ptr << std::endl; 14 | return 42; 15 | }) 16 | }; 17 | assert_eq!(r, 42) 18 | } 19 | -------------------------------------------------------------------------------- /test/src/header.h: -------------------------------------------------------------------------------- 1 | #ifndef header_h__ 2 | #define header_h__ 3 | 4 | #include 5 | #include 6 | 7 | #if __cplusplus > 199711L 8 | #include 9 | typedef std::atomic counter_t; 10 | #define COUNTER_STATIC static 11 | #else 12 | typedef int counter_t; 13 | #endif 14 | 15 | // This counter is incremented by destructors and constructors 16 | // and must be 0 at the end of the program 17 | inline counter_t &counter() { 18 | static counter_t counter; 19 | struct CheckCounter { 20 | ~CheckCounter() { 21 | assert(counter == 0); 22 | } 23 | }; 24 | static CheckCounter checker; 25 | return counter; 26 | } 27 | 28 | // class with destructor and copy constructor 29 | class A { 30 | public: 31 | int a; 32 | int b; 33 | A(int a, int b) : a(a), b(b) { counter()++; } 34 | A(const A &cpy) : a(cpy.a), b(cpy.b) { counter()++; } 35 | ~A() { counter()--; } 36 | #if !defined (_MSC_VER) || (_MSC_VER + 0 >= 1900) 37 | A &operator=(const A&) = default; 38 | #endif 39 | void setValues(int _a, int _b) { a = _a; b = _b; } 40 | int multiply() const { return a * b; } 41 | }; 42 | 43 | // Simple struct without a destructor or copy constructor 44 | struct B { 45 | int a; 46 | int b; 47 | }; 48 | 49 | struct MoveOnly { 50 | MoveOnly(int a = 8, int b = 9) : data(a,b) { } 51 | #if !defined (_MSC_VER) || (_MSC_VER + 0 >= 1900) 52 | MoveOnly(const MoveOnly &) = delete ; 53 | MoveOnly& operator=(const MoveOnly &) = delete ; 54 | MoveOnly(MoveOnly &&other) : data(other.data) { } 55 | MoveOnly& operator=(MoveOnly &&other) { data = other.data; return *this; } 56 | #endif 57 | A data; 58 | }; 59 | 60 | 61 | #endif // defined(header_h__) 62 | -------------------------------------------------------------------------------- /test/src/inner/explicit_path.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | pub fn explicit_path(im_explicit_path: i32) -> i32 { 4 | unsafe { 5 | cpp!([im_explicit_path as "int"] -> i32 as "int" { 6 | return im_explicit_path; 7 | }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/src/inner/innerinner.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | pub fn innerinner() -> i32 { 4 | unsafe { 5 | let im_inner_inner: i32 = 10; 6 | cpp! {[im_inner_inner as "int"] -> i32 as "int" { 7 | return im_inner_inner; 8 | }} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/src/inner/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), allow(dead_code, unused_imports))] 2 | 3 | use cpp::cpp; 4 | 5 | pub mod innerinner; 6 | 7 | #[path = "explicit_path.rs"] 8 | pub mod innerpath; 9 | 10 | pub fn inner() -> i32 { 11 | unsafe { 12 | let x: i32 = 10; 13 | cpp! {[x as "int"] -> i32 as "int" { 14 | return x; 15 | }} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/src/inner_sibling.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | pub mod child; 4 | pub mod child2; 5 | 6 | pub fn inner_sibling() -> i32 { 7 | unsafe { 8 | let x_s: i32 = 10; 9 | cpp! {[x_s as "int"] -> i32 as "int" { 10 | return x_s; 11 | }} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/inner_sibling/child.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | pub fn inner_sibling_child() -> i32 { 4 | unsafe { 5 | let x_c: i32 = 20; 6 | cpp! {[x_c as "int"] -> i32 as "int" { 7 | return x_c; 8 | }} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/src/inner_sibling/child2/mod.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | pub fn inner_sibling_child2() -> i32 { 4 | cpp! {unsafe [] -> i32 as "int" { 5 | return -44; 6 | }} 7 | } 8 | -------------------------------------------------------------------------------- /test/src/invalid_code.rs: -------------------------------------------------------------------------------- 1 | cpp! {{ 2 | this is not a valid cpp code! 3 | }} 4 | -------------------------------------------------------------------------------- /test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "512"] 2 | #![cfg_attr(not(test), allow(dead_code, unused_imports, clippy::needless_return))] 3 | 4 | use cpp::{cpp, cpp_class}; 5 | 6 | #[cfg(test)] 7 | mod inner; 8 | 9 | #[cfg(test)] 10 | mod inner_sibling; 11 | 12 | // Test that module resolution works correctly with inline modules. 13 | #[cfg(test)] 14 | mod nomod { 15 | pub mod inner; 16 | } 17 | 18 | // This non-existent module should not be parsed 19 | #[cfg(feature = "non_existent")] 20 | mod non_existent; 21 | 22 | // This module with invalid cpp code should not be parsed 23 | #[cfg(feature = "non_existent")] 24 | mod invalid_code; 25 | 26 | fn add_two(x: i32) -> i32 { 27 | x + 2 28 | } 29 | 30 | mod examples; 31 | 32 | cpp! {{ 33 | #define _USE_MATH_DEFINES 34 | #include 35 | #include "src/header.h" 36 | #include 37 | #include 38 | 39 | int global_int; 40 | 41 | int callRust1(int x) { 42 | return rust!(addTwoCallback [x : i32 as "int"] -> i32 as "int" { add_two(x) }); 43 | } 44 | void *callRust2(void *ptr) { 45 | int a = 3; 46 | typedef int LocalInt; 47 | typedef void * VoidStar; 48 | return rust!(ptrCallback [ptr : *mut u32 as "void*", a : u32 as "LocalInt"] 49 | -> *mut u32 as "VoidStar" { 50 | unsafe {*ptr += a}; 51 | ptr 52 | }); 53 | } 54 | int callRustExplicitReturn(int x) { 55 | return rust!(explicitReturnCallback [x : i32 as "int"] -> i32 as "int" { 56 | if x == 0 { 57 | return 42; 58 | } 59 | x + 1 60 | }); 61 | } 62 | }} 63 | 64 | cpp_class!( 65 | /// Documentation comments 66 | /** More /*comments*/ */ 67 | pub unsafe struct A as "A"); 68 | 69 | impl A { 70 | fn new(a: i32, b: i32) -> Self { 71 | unsafe { 72 | return cpp!([a as "int", b as "int"] -> A as "A" { 73 | return A(a, b); 74 | }); 75 | } 76 | } 77 | 78 | fn set_values(&mut self, a: i32, b: i32) { 79 | unsafe { 80 | return cpp!([self as "A*", a as "int", b as "int"] { 81 | self->setValues(a, b); 82 | }); 83 | } 84 | } 85 | 86 | fn multiply(&self) -> i32 { 87 | unsafe { 88 | return cpp!([self as "const A*"] -> i32 as "int" { 89 | return self->multiply(); 90 | }); 91 | } 92 | } 93 | } 94 | 95 | cpp! {{ 96 | bool callRust3(const A &a, int val) { 97 | A a2 = rust!(ACallback [a : A as "A", val : i32 as "int"] -> A as "A" 98 | { 99 | let mut a2 = a.clone(); 100 | a2.set_values(a.multiply(), val); 101 | a2 102 | }); 103 | return a2.a == a.a*a.b && a2.b == val; 104 | } 105 | 106 | int manyOtherTest() { 107 | int val = 32; 108 | int *v = &val; 109 | // returns void 110 | rust!(xx___1 [v : &mut i32 as "int*"] { *v = 43; } ); 111 | if (val != 43) return 1; 112 | rust!(xx___2 [val : &mut i32 as "int&"] { assert!(*val == 43); *val = 54; } ); 113 | if (val != 54) return 2; 114 | rust!(xx___3 [v : *mut i32 as "int*"] { unsafe {*v = 73;} } ); 115 | if (val != 73) return 3; 116 | rust!(xx___4 [val : *mut i32 as "int&"] { unsafe { assert!(*val == 73); *val = 62; }} ); 117 | if (val != 62) return 4; 118 | rust!(xx___5 [val : *const i32 as "const int&"] { unsafe { assert!(*val == 62); }} ); 119 | rust!(xx___6 [val : &i32 as "const int&"] { assert!(*val == 62); } ); 120 | rust!(xx___7 [val : i32 as "int"] { let v = val; assert!(v == 62); } ); 121 | // operations on doubles 122 | double fval = 5.5; 123 | double res = rust!(xx___8 [fval : f64 as "double"] -> f64 as "double" { fval * 1.2 + 9.9 } ); 124 | if (int((res - (5.5 * 1.2 + 9.9)) * 100000) != 0) return 5; 125 | res = rust!(xx___9 [fval : &mut f64 as "double&"] -> f64 as "double" { *fval *= 2.2; 8.8 } ); 126 | if (int((res - (8.8)) * 100000) != 0) return 9; 127 | if (int((fval - (5.5 * 2.2)) * 100000) != 0) return 10; 128 | // with a class 129 | A a(3,4); 130 | rust!(xx___10 [a : A as "A"] { let a2 = a.clone(); assert!(a2.multiply() == 12); } ); 131 | rust!(xx___11 [a : A as "A"] { let _a = a.clone(); } ); 132 | return 0; 133 | } 134 | }} 135 | 136 | #[test] 137 | fn captures() { 138 | let x: i32 = 10; 139 | let mut y: i32 = 20; 140 | let z = unsafe { 141 | cpp! {[x as "int", mut y as "int"] -> i64 as "long long int" { 142 | y += 1; 143 | return [&] { return x + y; }(); 144 | }} 145 | }; 146 | assert_eq!(x, 10); 147 | assert_eq!(y, 21); 148 | assert_eq!(z, 31); 149 | } 150 | 151 | #[test] 152 | fn no_captures() { 153 | cpp! {unsafe [] { global_int = 33; }}; 154 | 155 | let x = unsafe { 156 | cpp![[] -> i32 as "int" { 157 | return 10 + global_int; 158 | }] 159 | }; 160 | assert_eq!(x, 43); 161 | } 162 | 163 | #[test] 164 | fn duplicates() { 165 | // Test that we can call two captures with the same tokens 166 | 167 | let fn1 = |x| { 168 | cpp! { unsafe [x as "int"] -> i32 as "int" { 169 | static int sta; 170 | sta += x; 171 | return sta; 172 | }} 173 | }; 174 | let fn2 = |x| { 175 | cpp! { unsafe [x as "int"] -> i32 as "int" { 176 | static int sta; 177 | sta += x; 178 | return sta; 179 | }} 180 | }; 181 | assert_eq!(fn1(8), 8); 182 | assert_eq!(fn1(2), 10); 183 | 184 | // Since both the cpp! inside fn1 and fn2 are made of the same token, the same 185 | // function is actually generated, meaning they share the same static variable. 186 | // This might be confusing, I hope nobody relies on this behavior. 187 | assert_eq!(fn2(1), 11); 188 | } 189 | 190 | #[test] 191 | fn test_inner() { 192 | let x = inner::inner(); 193 | assert_eq!(x, 10); 194 | let y = inner::innerinner::innerinner(); 195 | assert_eq!(y, 10); 196 | let y = inner::innerpath::explicit_path(10); 197 | assert_eq!(y, 10); 198 | } 199 | 200 | #[test] 201 | fn inner_sibling() { 202 | let x = inner_sibling::inner_sibling(); 203 | assert_eq!(x, 10); 204 | let y = inner_sibling::child::inner_sibling_child(); 205 | assert_eq!(y, 20); 206 | let z = inner_sibling::child2::inner_sibling_child2(); 207 | assert_eq!(z, -44); 208 | } 209 | 210 | #[test] 211 | fn includes() { 212 | unsafe { 213 | let pi = cpp!([] -> f32 as "float" { 214 | return M_PI; 215 | }); 216 | assert!(pi - ::std::f32::consts::PI < 0.0000000001); 217 | } 218 | } 219 | 220 | #[test] 221 | fn plusplus() { 222 | unsafe { 223 | let mut x: i32 = 0; 224 | cpp!([mut x as "int"] { 225 | x++; 226 | }); 227 | assert_eq!(x, 1); 228 | } 229 | } 230 | 231 | #[test] 232 | fn test_nomod() { 233 | assert_eq!(nomod::inner::nomod_inner(), 10); 234 | } 235 | 236 | #[test] 237 | fn rust_submacro() { 238 | let result = unsafe { cpp!([] -> i32 as "int" { return callRust1(45); }) }; 239 | assert_eq!(result, 47); // callRust1 adds 2 240 | 241 | let mut val: u32 = 18; 242 | { 243 | let val_ref = &mut val; 244 | let result = unsafe { 245 | cpp!([val_ref as "void*"] -> bool as "bool" { 246 | return callRust2(val_ref) == val_ref; 247 | }) 248 | }; 249 | assert_eq!(result, true); 250 | } 251 | assert_eq!(val, 21); // callRust2 does +=3 252 | 253 | let result = unsafe { cpp!([] -> i32 as "int" { return callRustExplicitReturn(0); }) }; 254 | assert_eq!(result, 42); 255 | let result = unsafe { cpp!([] -> i32 as "int" { return callRustExplicitReturn(9); }) }; 256 | assert_eq!(result, 10); 257 | 258 | let result = unsafe { 259 | cpp!([]->bool as "bool" { 260 | A a(5, 3); 261 | return callRust3(a, 18); 262 | }) 263 | }; 264 | assert!(result); 265 | 266 | let result = unsafe { 267 | cpp!([]->u32 as "int" { 268 | return manyOtherTest(); 269 | }) 270 | }; 271 | assert_eq!(result, 0); 272 | } 273 | 274 | pub trait MyTrait { 275 | fn compute_value(&self, x: i32) -> i32; 276 | } 277 | 278 | cpp! {{ 279 | struct MyClass { 280 | virtual int computeValue(int) const = 0; 281 | }; 282 | int operate123(MyClass *callback) { return callback->computeValue(123); } 283 | 284 | struct TraitPtr { void *a,*b; }; 285 | }} 286 | cpp! {{ 287 | class MyClassImpl : public MyClass { 288 | public: 289 | TraitPtr m_trait; 290 | int computeValue(int x) const /*override*/ { 291 | return rust!(MCI_computeValue [m_trait : &dyn MyTrait as "TraitPtr", x : i32 as "int"] 292 | -> i32 as "int" { 293 | m_trait.compute_value(x) 294 | }); 295 | } 296 | }; 297 | }} 298 | 299 | struct MyTraitImpl { 300 | x: i32, 301 | } 302 | impl MyTrait for MyTraitImpl { 303 | fn compute_value(&self, x: i32) -> i32 { 304 | self.x + x 305 | } 306 | } 307 | 308 | #[test] 309 | fn rust_submacro_trait() { 310 | let inst = MyTraitImpl { x: 333 }; 311 | let inst_ptr: &dyn MyTrait = &inst; 312 | let i = unsafe { 313 | cpp!([inst_ptr as "TraitPtr"] -> u32 as "int" { 314 | MyClassImpl mci; 315 | mci.m_trait = inst_ptr; 316 | return operate123(&mci); 317 | }) 318 | }; 319 | assert_eq!(i, 123 + 333); 320 | } 321 | 322 | #[test] 323 | fn witin_macro() { 324 | assert_eq!(unsafe { cpp!([] -> u32 as "int" { return 12; }) }, 12); 325 | let s = format!("hello{}", unsafe { cpp!([] -> u32 as "int" { return 14; }) }); 326 | assert_eq!(s, "hello14"); 327 | } 328 | 329 | #[test] 330 | fn with_unsafe() { 331 | let x = 45; 332 | assert_eq!(cpp!(unsafe [x as "int"] -> u32 as "int" { return x + 1; }), 46); 333 | } 334 | 335 | #[test] 336 | fn rust_submacro_closure() { 337 | let mut result = unsafe { 338 | cpp!([] -> i32 as "int" { 339 | auto x = rust!(bbb []-> A as "A" { A::new(5,7) }).multiply(); 340 | auto y = []{ A a(3,2); return rust!(aaa [a : A as "A"] -> i32 as "int" { a.multiply() }); }(); 341 | return x + y; 342 | }) 343 | }; 344 | assert_eq!(result, 5 * 7 + 3 * 2); 345 | 346 | unsafe { 347 | cpp!([mut result as "int"] { 348 | A a(9,2); 349 | rust!(Ccc [a : A as "A", result : &mut i32 as "int&"] { *result = a.multiply(); }); 350 | }) 351 | }; 352 | assert_eq!(result, 18); 353 | } 354 | 355 | pub mod cpp_class; 356 | -------------------------------------------------------------------------------- /test/src/nomod/inner.rs: -------------------------------------------------------------------------------- 1 | use cpp::cpp; 2 | 3 | pub fn nomod_inner() -> i32 { 4 | unsafe { 5 | let nomod_inner: i32 = 10; 6 | cpp! {[nomod_inner as "int"] -> i32 as "int" { 7 | return nomod_inner; 8 | }} 9 | } 10 | } 11 | --------------------------------------------------------------------------------