diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..98dd3df --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: CI +on: + pull_request: + push: + branches: + - master + +jobs: + msrv_solo: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install 1.70 toolchain + uses: dtolnay/rust-toolchain@1.70 + + - name: Check tests + run: cargo test --all-features --no-run && cargo test + + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run tests + run: bash scripts/tests-all-features.sh + + checks: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run tests + run: bash scripts/check-all-features.sh + + clippy: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run tests + run: bash scripts/clippy-all-features.sh + + package: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run tests + run: bash scripts/package-all-features.sh diff --git a/.gitignore b/.gitignore index ea8c4bf..048681c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +scratchpad.* diff --git a/.gitmodules b/.gitmodules index a4f936c..f53bbf6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "vendors/rsc"] - path = vendors/rsc +[submodule "src/vendors/rsc"] + path = src/vendors/rsc url = git@github.com:jymchng/rsc.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf98575..4d23ecc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,25 @@ repos: - id: fmt - id: cargo-check - id: clippy + + - repo: local + hooks: + - id: cargo-package + name: cargo-package + description: cargo package + entry: bash scripts/package-all-features.sh + language: system + types: [rust] + pass_filenames: false + - id: cargo-test + name: cargo-test + description: cargo test + entry: bash scripts/tests-all-features.sh + language: system + types: [rust] + pass_filenames: false + + # - repo: local # hooks: # - id: cargo-test diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index a64cf24..b2e77bb 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -27,3 +27,10 @@ language: system types: [rust] pass_filenames: false +# - id: cargo-package +# name: cargo-package +# description: cargo package +# entry: bash scripts/package-all-features.sh +# language: system +# types: [rust] +# pass_filenames: false diff --git a/Cargo.lock b/Cargo.lock index b6376aa..c09ccfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,13 +78,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rsc" -version = "3.0.0" -dependencies = [ - "peekmore", -] - [[package]] name = "ryu" version = "1.0.17" @@ -211,9 +204,9 @@ name = "typenum-consts" version = "0.1.0" dependencies = [ "dotenv", + "peekmore", "proc-macro2", "quote", - "rsc", "syn", "trybuild", "typenum", diff --git a/Cargo.toml b/Cargo.toml index a384461..4035573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,30 @@ name = "typenum-consts" version = "0.1.0" edition = "2021" - +authors = ["Jim Chng "] +exclude = [ + "dev-notes", + "assets", + "scripts", + "tests", + "trybuild_tests", + "wip", + "src/vendors/rsc/benches", + "src/vendors/rsc/src/bin", + "**/.gitignore", + "**/.gitmodules", + "**/.pre-commit-config.yaml", + "**/.pre-commit-hooks.yaml", + ".github/*", +] +rust-version = "1.70" +readme = "README.md" +repository = "https://github.com/jymchng/typenum-consts" +description = "Procedural macros that take a literal integer (or the result of an evaluation of simple mathematical expressions or an environment variable whose value is a literal integer) and convert it to a `typenum::Unsigned` / `typenum::Integer` type-level positive/negative/unsigned integer." +keywords = ["typenum", "type-level-integers", "proc-macro", "metaprogramming"] +categories = ["development-tools", "mathematics", "rust-patterns"] +documentation = "https://docs.rs/typenum-consts/latest/" +license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] @@ -13,13 +36,10 @@ proc-macro2 = "1.0.79" quote = "1.0.35" syn = { version = "2.0.53", features = ["extra-traits", "full"] } typenum = "1.17.0" -rsc = { path = "./vendors/rsc"} dotenv = "0.15.0" +peekmore = "1.0.0" [dev-dependencies] trybuild = "1.0.90" [features] -# "debug" feature is meant for development, to print debug statements easily -"debug" = [] -# "eval" = ["rsc"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..ffe3e3b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2024] [Jim Chng] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..98ff10d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2024] [Jim Chng] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index cba11ef..3a4e09d 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,49 @@ -# (WIP) typenum-consts +# typenum-consts -Procedural macro that takes a literal integer and converts it to a `typenum::Unsigned` / `typenum::ToInt` type-level positive/negative/unsigned integer. +
+ GitHub Workflow Status + Crates.io Version + docs.rs +
-## Examples +Procedural macros that take a literal integer (or the result of an evaluation of simple mathematical expressions or an environment variable whose value is a literal integer) and convert it to a `typenum::Unsigned` / `typenum::Integer` type-level positive/negative/unsigned integer. -```rust -use core::marker::PhantomData; -use typenum_consts::tnconst; +## Why? -#[cfg(target_pointer_width = "32")] -type I32OrI64 = i32; -#[cfg(target_pointer_width = "64")] -type I32OrI64 = i64; +1. It saves time. -type ActualPositive84938493Type = tnconst![+84938493]; +Assuming you want a type-level positive integer `84938493`, `tnconst![+84938493]` outputs directly `typenum::PInt` (by the way, `U84938493` does not exist in `typenum::consts`). The alternative is to type `PInt, ...>, ...>, ...>` (which argubly takes a lot more time). -type ExpectedPositive84938493Type = ::typenum::PInt< // `PInt` implies positive integer at the type level -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U7>, ::typenum::consts::U8>, // 10**7 * 8 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U6>, ::typenum::consts::U4>, // 10**6 * 4 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U5>, ::typenum::consts::U9>, // 10**5 * 9 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U4>, ::typenum::consts::U3>, // 10**4 * 3 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U3>, ::typenum::consts::U8>, // 10**3 * 8 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U2>, ::typenum::consts::U4>, // 10**2 * 4 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U1>, ::typenum::consts::U9>, // 10**1 * 9 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U0>, ::typenum::consts::U3>, // 10**0 * 3 -::typenum::consts::U0>>>>>>>> +Example: + +```rust +# use core::marker::PhantomData; +# use typenum_consts::tnconst; +# use typenum::*; +# +# #[cfg(target_pointer_width = "32")] +# type I32OrI64 = i32; +# #[cfg(target_pointer_width = "64")] +# type I32OrI64 = i64; +type ActualPositive84938493Type = tnconst![+84938493]; +type ExpectedPositive84938493Type = PInt< // `PInt` implies positive integer at the type level +Sum< +Prod, U8>, // 10**7 * 8 +Sum< +Prod, U4>, // 10**6 * 4 +Sum< +Prod, U9>, // 10**5 * 9 +Sum< +Prod, U3>, // 10**4 * 3 +Sum< +Prod, U8>, // 10**3 * 8 +Sum< +Prod, U4>, // 10**2 * 4 +Sum< +Prod, U9>, // 10**1 * 9 +Sum< +Prod, U3>, // 10**0 * 3 +U0>>>>>>>> >; typenum::assert_type_eq!(ExpectedPositive84938493Type, ActualPositive84938493Type); @@ -41,74 +52,50 @@ assert_eq!( >::INT ); ``` -```rust -use core::marker::PhantomData; -use typenum_consts::tnconst; - -#[cfg(target_pointer_width = "32")] -type I32OrI64 = i32; -#[cfg(target_pointer_width = "64")] -type I32OrI64 = i64; -type ActualNegative84938493Type = tnconst![-84938493]; - -type ExpectedNegative84938493Type = ::typenum::NInt< // `NInt` implies Negative integer at the type level -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U7>, ::typenum::consts::U8>, // 10**7 * 8 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U6>, ::typenum::consts::U4>, // 10**6 * 4 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U5>, ::typenum::consts::U9>, // 10**5 * 9 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U4>, ::typenum::consts::U3>, // 10**4 * 3 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U3>, ::typenum::consts::U8>, // 10**3 * 8 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U2>, ::typenum::consts::U4>, // 10**2 * 4 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U1>, ::typenum::consts::U9>, // 10**1 * 9 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U0>, ::typenum::consts::U3>, // 10**0 * 3 -::typenum::consts::U0>>>>>>>> ->; -typenum::assert_type_eq!(ExpectedNegative84938493Type, ActualNegative84938493Type); -assert_eq!( - >::INT, - >::INT -); -``` +2. For conditional compilation. + +Suppose in different environments you want a different type-level integer, you can either use `#[cfg(production)] type NUMBER = U69;` or you can do the following: + ```rust -use core::marker::PhantomData; -use typenum_consts::tnconst; - -#[cfg(target_pointer_width = "32")] -type I32OrI64 = i32; -#[cfg(target_pointer_width = "64")] -type I32OrI64 = i64; - -type ActualUnsigned84938493Type = tnconst![84938493]; // No sign at the front means Unsigned - -type ExpectedUnsigned84938493Type = ::typenum::Sum< // No `PInt` or `NInt` implies Unsigned integer at the type level -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U7>, ::typenum::consts::U8>, // 10**7 * 8 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U6>, ::typenum::consts::U4>, // 10**6 * 4 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U5>, ::typenum::consts::U9>, // 10**5 * 9 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U4>, ::typenum::consts::U3>, // 10**4 * 3 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U3>, ::typenum::consts::U8>, // 10**3 * 8 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U2>, ::typenum::consts::U4>, // 10**2 * 4 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U1>, ::typenum::consts::U9>, // 10**1 * 9 -::typenum::Sum< -::typenum::Prod<::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U0>, ::typenum::consts::U3>, // 10**0 * 3 -::typenum::consts::U0>>>>>>>>; - -typenum::assert_type_eq!(ExpectedUnsigned84938493Type, ActualUnsigned84938493Type); -assert_eq!( - ::USIZE, - ::USIZE, -); +use typenum::{U69, assert_type_eq}; +use typenum_consts::uconst; +// ``` .env +// ENV_VAR=69 +// ``` +type E = uconst![env!("ENV_VAR");]; +assert_type_eq!(E, U69); ``` + +All four macros, namely, `tnconst![...]`, `pconst![...]`, `uconst![...]` and `nconst![...]`, can read literal integers from the environment. + +# Vendored Crates + +## [`rsc`](https://github.com/fivemoreminix/rsc/commit/67c4ddffbe45a30de0fd696c569de885bfd4e9b4) version 3.0.0 + +Reasons for vendoring `src`. + +1. As of 28 March 2024, there is a version 3.0.0 of the crate on the GitHub [repository](https://github.com/fivemoreminix/rsc/commit/67c4ddffbe45a30de0fd696c569de885bfd4e9b4) but without corresponding crate on `crates.io`. +2. Easier to implement `Num` for `isize` with vendoring. +3. `typenum-needs a mathematical expression evaluator. +4. Its [license](https://github.com/fivemoreminix/rsc/tree/67c4ddffbe45a30de0fd696c569de885bfd4e9b4?tab=readme-ov-file#license) allows for 'usage without attribution'. Anyway, `src/vendors/rsc/Cargo.toml.vendored` is the original `Cargo.toml` file found in the repository. +5. Thanks to [Luke Wilson](https://github.com/fivemoreminix). + +## License + +Licensed under either of + + * Apache License, Version 2.0 + + [[LICENSE-APACHE]( http://www.apache.org/licenses/LICENSE-2.0)] + * MIT license + + [[LICENSE-MIT](http://opensource.org/licenses/MIT)] + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/assets/ss-6.png b/assets/ss-6.png new file mode 100644 index 0000000..b678598 Binary files /dev/null and b/assets/ss-6.png differ diff --git a/assets/ss-7.png b/assets/ss-7.png new file mode 100644 index 0000000..7b508c0 Binary files /dev/null and b/assets/ss-7.png differ diff --git a/assets/ss-8.png b/assets/ss-8.png new file mode 100644 index 0000000..15b23f1 Binary files /dev/null and b/assets/ss-8.png differ diff --git a/assets/ss-9.png b/assets/ss-9.png new file mode 100644 index 0000000..a2937f9 Binary files /dev/null and b/assets/ss-9.png differ diff --git a/dev-notes/learnings.md b/dev-notes/learnings.md new file mode 100644 index 0000000..bf34b75 --- /dev/null +++ b/dev-notes/learnings.md @@ -0,0 +1,27 @@ +# Cargo test only 1 test + +cargo test --features debug --test test_eager_invoke -- test_eager_invoke --exact --nocapture + +# Parsing Vec and Values + +https://stackoverflow.com/a/76089971 + +# Eager proc-macro Expansion + +https://stackoverflow.com/questions/77153497/eager-proc-macro-expansion/77160978#77160978 + +# Run tests with debug prints + +export ENV_VAR=69 && cargo test --config 'build.rustflags=["--cfg", "__debug_tnconst"]' + +# Run tests without debug prints + +export ENV_VAR=69 && cargo test + +# Vendoring doesn't work + +1. Vendor specific crate from GitHub doesn't work - https://github.com/rust-lang/cargo/issues/9234 +2. Git submodule add and checkout-ed to specific commit works +3. but, even after replacing Cargo.toml's [source.], `cargo package` still attempts to find the dep on `crates.io` + +https://users.rust-lang.org/t/publishing-a-package-with-a-vendored-crate-but-is-not-listed-as-a-dependency-because-the-vendored-crate-author-did-not-publish-his-crate-onto-crates-io-lol/108971/1 diff --git a/scripts/check-all-features.sh b/scripts/check-all-features.sh index 2570758..7e6ab89 100644 --- a/scripts/check-all-features.sh +++ b/scripts/check-all-features.sh @@ -1,7 +1,7 @@ #!/bin/bash # Array of feature names -features=("debug") +features=() # Calculate the total number of features total_features=${#features[@]} diff --git a/scripts/clippy-all-features.sh b/scripts/clippy-all-features.sh index 45e2b0f..f114fd7 100644 --- a/scripts/clippy-all-features.sh +++ b/scripts/clippy-all-features.sh @@ -1,7 +1,7 @@ #!/bin/bash # Array of feature names -features=("debug") +features=() # Calculate the total number of features total_features=${#features[@]} diff --git a/scripts/package-all-features.sh b/scripts/package-all-features.sh new file mode 100644 index 0000000..700285c --- /dev/null +++ b/scripts/package-all-features.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Array of feature names +features=() + +# Calculate the total number of features +total_features=${#features[@]} + +# Export `ENV_VAR` +export ENV_VAR="69" + +# Function to generate combinations of features +generate_combinations() { + local index=$1 + local combination=$2 + + if [ $index -eq $total_features ]; then + # Run cargo package with the current combination of features + echo "Running: cargo package --features $combination" + cargo package --features "$combination" --allow-dirty && cargo package --features "$combination" --list --allow-dirty + else + # Include the current feature in the combination and recurse + generate_combinations "$((index + 1))" "$combination ${features[index]}" + # Exclude the current feature and recurse + generate_combinations "$((index + 1))" "$combination" + fi +} + +# Start generating combinations from index 0 +generate_combinations 0 "" + +echo "All feature combinations packagee successfully." diff --git a/scripts/tests-all-features.sh b/scripts/tests-all-features.sh index f7a8a6d..a4bc2b3 100644 --- a/scripts/tests-all-features.sh +++ b/scripts/tests-all-features.sh @@ -1,7 +1,7 @@ #!/bin/bash # Array of feature names -features=("debug") +features=() # Calculate the total number of features total_features=${#features[@]} diff --git a/scripts/update-vendors.sh b/scripts/update-vendors.sh new file mode 100644 index 0000000..83c03bb --- /dev/null +++ b/scripts/update-vendors.sh @@ -0,0 +1,6 @@ +cd vendors/rsc +git commit -am "update" +git push +cd ../.. +git commit -am "updated vendor" +git push diff --git a/src/ast.rs b/src/ast.rs index 6fa8101..80e064f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,10 +1,11 @@ use crate::{ast_macro::AllowedMacros, debug_eprintln}; +use quote::ToTokens; use syn::{ braced, parse::{Parse, ParseStream}, spanned::Spanned, token::Brace, - Block, Ident, LitInt, Macro, Result, Stmt, Token, + Block, Error, Ident, LitInt, Macro, Result, Stmt, Token, }; pub(crate) enum MathExprs { @@ -61,7 +62,55 @@ fn which_lit_integer_or_exprs(input: ParseStream, sign: Sign) -> Result()?; let allowed_macro = AllowedMacros::which_macro(&some_macro)?; - let litint = LitInt::new(&allowed_macro.invoke_macro()?, some_macro.span()); + let macro_result = &allowed_macro.invoke_macro()?; + let macro_result_as_isize = macro_result.parse::().map_err(|err| { + Error::new( + some_macro.span(), + format!( + "unable to parse the output of `{}` macro invocation to `isize`; error: {err}", + some_macro.path.to_token_stream() + ), + ) + })?; + match sign { + Sign::N => { + if macro_result_as_isize > 0 { + return Err(Error::new( + some_macro.span(), + format!( + "invocation of `{}` macro does not return a negative integer literal", + some_macro.path.to_token_stream() + ), + )); + } + } + Sign::P => { + if macro_result_as_isize < 0 { + return Err(Error::new( + some_macro.span(), + format!( + "invocation of `{}` macro does not return a positive integer literal", + some_macro.path.to_token_stream() + ), + )); + } + } + Sign::U => { + if macro_result_as_isize < 0 { + return Err(Error::new( + some_macro.span(), + format!( + "invocation of `{}` macro does not return a positive integer literal", + some_macro.path.to_token_stream() + ), + )); + } + } + } + let litint = LitInt::new( + format!("{}", macro_result_as_isize).as_str(), + some_macro.span(), + ); Ok(sign.lit_integer(litint)?) } else { Ok(sign.lit_integer(input.parse::()?)?) @@ -90,6 +139,60 @@ impl Parse for LitIntegerOrExprs { } } +pub(crate) struct NegativeLitIntegerOrExprs(pub(crate) LitIntegerOrExprs); + +impl Parse for NegativeLitIntegerOrExprs { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![+]) || lookahead.peek(Token![-]) { + return Err(Error::new( + lookahead.error().span(), + "when using `nconst`, the first character passed cannot be a `-`", + )); + } + Ok(NegativeLitIntegerOrExprs(which_lit_integer_or_exprs( + input, + Sign::N, + )?)) + } +} + +pub(crate) struct UnsignedLitIntegerOrExprs(pub(crate) LitIntegerOrExprs); + +impl Parse for UnsignedLitIntegerOrExprs { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![+]) || lookahead.peek(Token![-]) { + return Err(Error::new( + lookahead.error().span(), + "when using `uconst`, the first character passed cannot be a `-` or a `+`", + )); + } + Ok(UnsignedLitIntegerOrExprs(which_lit_integer_or_exprs( + input, + Sign::U, + )?)) + } +} + +pub(crate) struct PositiveLitIntegerOrExprs(pub(crate) LitIntegerOrExprs); + +impl Parse for PositiveLitIntegerOrExprs { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![+]) || lookahead.peek(Token![-]) { + return Err(Error::new( + lookahead.error().span(), + "when using `pconst`, the first character passed cannot be a `+`", + )); + } + Ok(PositiveLitIntegerOrExprs(which_lit_integer_or_exprs( + input, + Sign::P, + )?)) + } +} + pub(crate) enum LitIntegerOrExprs { Exprs(MathExprs), LitInteger(LitInteger), diff --git a/src/ast_macro.rs b/src/ast_macro.rs index 0a196d7..d98827f 100644 --- a/src/ast_macro.rs +++ b/src/ast_macro.rs @@ -6,6 +6,12 @@ use std::path::PathBuf; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::{LitStr, Macro, Token}; +const CARGO_BUILD_TARGET_DIR: &str = "CARGO_BUILD_TARGET_DIR"; +const CARGO_MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR"; +const CARGO_TARGET_DIR: &str = "CARGO_TARGET_DIR"; + +const CARGO_ENV_DIRS: [&str; 3] = [CARGO_BUILD_TARGET_DIR, CARGO_MANIFEST_DIR, CARGO_TARGET_DIR]; + impl EnvArgs { fn check_string_cannot_be_empty( &self, @@ -18,6 +24,22 @@ impl EnvArgs { } Ok(()) } + + fn get_env_path(&self, mut env_path: PathBuf) -> core::result::Result { + let mut err_array: [PathBuf; 3] = [PathBuf::new(), PathBuf::new(), PathBuf::new()]; + for (idx, cargo_env_dir) in CARGO_ENV_DIRS.iter().enumerate() { + if let Ok(manifest_dir) = env::var(cargo_env_dir) { + let manifest_path = PathBuf::from(manifest_dir); + env_path = manifest_path.join(env_path); + err_array[idx] = env_path.clone(); + debug_eprintln!("`env_path` = {env_path:?}; `manifest_path` = {manifest_path:?}"); + if env_path.exists() { + return Ok(env_path); + } + }; + } + Err(err_array) + } pub fn read_env_value(&self) -> Result { let litkey = &self.key; let litfile_path = &self.file_path; @@ -26,33 +48,39 @@ impl EnvArgs { if let Some(litpath) = litfile_path { let path = &litpath.value(); self.check_string_cannot_be_empty(path, litpath.span(), "path")?; - let mut env_path = PathBuf::from(path); - if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { - let manifest_path = PathBuf::from(manifest_dir); - env_path = manifest_path.join(env_path); - debug_eprintln!("`env_path` = {env_path:?}; `manifest_path` = {manifest_path:?}"); - }; - - dotenv::from_path(&env_path).map_err(|e| { - Error::new( - litpath.span(), - format!("failed to read file = `{}`: err: {}", env_path.display(), e), - ) - })?; - #[allow(clippy::needless_return)] - return env::var(&key).map_err(|e| { - Error::new( - litkey.span(), - format!( - "failed to read `{}` key from `{}` file, err = {e}", - key, - env_path.display() - ), - ) - }); + let env_path = PathBuf::from(path); + match self.get_env_path(env_path.clone()) { + Ok(env_path) => { + dotenv::from_path(&env_path).map_err(|e| { + Error::new( + litpath.span(), + format!( + "failed to read file = `{}`: err: {}", + &env_path.display(), + e + ), + ) + })?; + env::var(&key).map_err(|e| { + Error::new( + litkey.span(), + format!( + "failed to read `{}` key from `{}` file, err = {e}", + key, + &env_path.display() + ), + ) + }) + } + Err(err) => { + Err(Error::new( + litpath.span(), + format!("failed to locate the file = `{}`, the 3 file paths which are searched for are `{err:?}`", &env_path.display()), + )) + } + } } else { - #[allow(clippy::needless_return)] - return env::var(&key).map_err(|e| { + env::var(&key).map_err(|e| { Error::new( litkey.span(), format!( @@ -60,7 +88,7 @@ impl EnvArgs { key ), ) - }); + }) } } } diff --git a/src/exprs_impl.rs b/src/exprs_impl.rs index 63f77d8..661959a 100644 --- a/src/exprs_impl.rs +++ b/src/exprs_impl.rs @@ -1,7 +1,7 @@ use crate::ast::{LitInteger, MathExprs}; +use crate::rsc::{parse, tokenize, Interpreter, Variant}; use proc_macro2::Span; use quote::ToTokens; -use rsc::{parse, tokenize, Interpreter, Variant}; use syn::{spanned::Spanned, Error, LitInt, Result, Stmt}; const ANS: &str = "ANS"; @@ -51,10 +51,15 @@ fn interpreter_eval(math_stmts: &[Stmt]) -> Result { } fn eval_exprs_neg(math_stmts: Vec) -> Result { - let mut final_ans = interpreter_eval(&math_stmts)?; + let final_ans = interpreter_eval(&math_stmts)?; let last_stmt = get_last_stmt(&math_stmts)?; - if final_ans <= 0 { - final_ans = -final_ans; + if final_ans > 0 { + return Err(Error::new( + last_stmt.span(), + format!( + "expressions should be evaluated to a negative integer, got `{final_ans}` instead" + ), + )); }; Ok(LitInteger::Negative { lit_integer: LitInt::new(format! {"{}", final_ans}.as_str(), last_stmt.span()), @@ -89,6 +94,7 @@ fn eval_exprs_pos(math_stmts: Vec) -> Result { fn eval_exprs_usig(math_stmts: Vec) -> Result { let final_ans = interpreter_eval(&math_stmts)?; let last_stmt = get_last_stmt(&math_stmts)?; + if final_ans < 0 { return Err(Error::new( last_stmt.span(), diff --git a/src/lib.rs b/src/lib.rs index 7f3ccb5..c0fbbf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,10 @@ #![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -use crate::ast::LitIntegerOrExprs; +use crate::ast::{ + LitIntegerOrExprs, NegativeLitIntegerOrExprs, PositiveLitIntegerOrExprs, + UnsignedLitIntegerOrExprs, +}; use crate::macros::debug_eprintln; use proc_macro::{self, TokenStream}; use tnconst_impl::{ @@ -9,6 +12,8 @@ use tnconst_impl::{ pconst_impl_math_exprs, tnconst_impl_lit_integer, tnconst_impl_math_exprs, uconst_impl_lit_integer, uconst_impl_math_exprs, }; +#[allow(clippy::all)] +use vendors::rsc; mod ast; mod ast_macro; @@ -16,15 +21,95 @@ mod exprs_impl; mod macros; mod tnconst_impl; mod uconst_impl; +mod vendors; -#[cfg(debug_assertions)] -#[cfg(feature = "debug")] -extern crate std; - +/// [`uconst!`] is a procedural macro that converts a literal integer or an expression into a [`typenum`]'s type-level unsigned integer (i.e. the type implements the [`typenum::Unsigned`] trait). +/// +/// There are three ways you can invoke this macro. +/// +/// ## 1. Invoke it with a literal integer +/// +/// ```rust +/// use typenum::{U123, assert_type_eq}; +/// use typenum_consts::uconst; +/// +/// type A = uconst![123]; +/// assert_type_eq!(A, U123); +/// ``` +/// +/// Compilation fails if the literal integer is prefixed with either a `-` or a `+`. +/// +/// ```compile_fail +/// # use typenum::{U123, assert_type_eq}; +/// # use typenum_consts::uconst; +/// type B = uconst![+123]; // Fail to compile +/// ``` +/// +/// ```compile_fail +/// # use typenum::{U123, assert_type_eq}; +/// # use typenum_consts::uconst; +/// type C = uconst![-123]; // Fail to compile +/// ``` +/// +/// ## 2. Invoke using an expression or many simple mathematical expressions +/// +/// ```rust +/// use typenum::{U15, assert_type_eq}; +/// use typenum_consts::uconst; +/// type D = uconst![{ +/// a = 10; +/// b = 5; +/// a + b; // Last statement is always the final returned value to be casted into `typenum` type-level integer, U15 +/// }]; +/// assert_type_eq!(D, U15); +/// ``` +/// +/// It is a compilation error if the mathematical expressions evaluate to a negative literal integer. +/// +/// ```compile_fail +/// use typenum::{U15, assert_type_eq}; +/// use typenum_consts::uconst; +/// type D = uconst![{ +/// a = 10; +/// b = 5; +/// b - a; // 5 - 10 = -5, cannot be made into a `U15` +/// }]; +/// assert_type_eq!(D, U15); +/// ``` +/// +/// ## 3. Invoke by reading from an environment variable +/// +/// Note: `env!(...)` is a macro-like invocation. The first parameter is mandatory and is the key of the environment variable that `uconst` will read. The second parameter is optional and is the file path of the `.env.*` file to read the environment variable from, e.g. `env!("ENV_VAR", "./.env.prod")`, `"ENV_VAR"` is the key to read the value from and `"./.env.prod"` is the file path relative to [`CARGO_MANIFEST_DIR`]. +/// +/// ```rust +/// use typenum::{U69, assert_type_eq}; +/// use typenum_consts::uconst; +/// // ``` .env +/// // ENV_VAR=69 +/// // ``` +/// type E = uconst![env!("ENV_VAR");]; +/// assert_type_eq!(E, U69); +/// ``` +/// +/// It is a compilation error if the environment variable evaluate to a negative literal integer. +/// +/// ```compile_fail +/// use typenum::{U69, assert_type_eq}; +/// use typenum_consts::uconst; +/// // ``` .env +/// // NENV_VAR=-69 +/// // ``` +/// type F = uconst![env!("NENV_VAR");]; +/// assert_type_eq!(F, U69); +/// ``` +/// +/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates +/// [`typenum::Unsigned`]: https://docs.rs/typenum/latest/typenum/marker_traits/trait.Unsigned.html +/// [`typenum`]: https://github.com/paholg/typenum/tree/main/src #[proc_macro] pub fn uconst(items: TokenStream) -> TokenStream { - match syn::parse::(items) { - Ok(litint_exprs) => match litint_exprs { + match syn::parse::(items) { + Ok(litint_exprs) => match litint_exprs.0 { LitIntegerOrExprs::Exprs(math_exprs) => uconst_impl_math_exprs(math_exprs) .unwrap_or_else(syn::Error::into_compile_error) .into(), @@ -36,6 +121,87 @@ pub fn uconst(items: TokenStream) -> TokenStream { } } +/// [`tnconst!`] is a procedural macro that converts a literal integer or an expression into a [`typenum`]'s type-level unsigned/positive/negative (depending on what is the prefix-sign) integer (i.e. the type implements the [`typenum::Unsigned`]/[`typenum::Integer`] trait). +/// +/// Because [`tnconst!`] can be evaluated into [`typenum::UInt`], [`typenum::PInt`] and [`typenum::NInt`], to disambiguate them, one is required to invoke the macro with either `+`, `-` to get [`tnconst!`] to evaluate the macro input as [`typenum::PInt`] or [`typenum::NInt`], respectively. [`typenum::NInt`] is the default and does not require any sign to be prefixed. +/// +/// Examples: +/// +/// If you invoke e.g. `tnconst![123]`, i.e. there is no sign prefixing the literal integer `123`, then this will be evaluated as a [`typenum::UInt`], specifically, [`typenum::U123`]. +/// +/// If you invoke e.g. `tnconst![-123]`, i.e. there is a negative sign prefixing the literal integer `123`, then this will be evaluated as a [`typenum::NInt`], specifically, [`typenum::N123`]. +/// +/// There are three ways you can invoke this macro. +/// +/// ## 1. Invoke it with a literal integer +/// +/// ```rust +/// use typenum::{N123, assert_type_eq}; +/// use typenum_consts::tnconst; +/// +/// type A = tnconst![-123]; +/// assert_type_eq!(A, N123); +/// ``` +/// +/// ## 2. Invoke using an expression or many simple mathematical expressions +/// +/// ```rust +/// use typenum::{P15, assert_type_eq}; +/// use typenum_consts::tnconst; +/// type D = tnconst![+ { +/// a = 10; +/// b = 5; +/// a + b; // Last statement is always the final returned value to be casted into `typenum` type-level integer, P15 +/// }]; +/// assert_type_eq!(D, P15); +/// ``` +/// +/// It is a compilation error if the mathematical expressions evaluate to a negative (positive) literal integer and you had specify a `+` (`-`) prefix to ask [`tnconst!`] to evaluate the input as a [`typenum::PInt`] ([`typenum::NInt`]). +/// +/// ```compile_fail +/// use typenum::{P15, assert_type_eq}; +/// use typenum_consts::tnconst; +/// type D = tnconst![+ { +/// a = 10; +/// b = 5; +/// b - a; // 5 - 10 = -5, cannot be made into a `P15` +/// }]; +/// assert_type_eq!(D, P15); +/// ``` +/// +/// ## 3. Invoke by reading from an environment variable +/// +/// Note: `env!(...)` is a macro-like invocation. The first parameter is mandatory and is the key of the environment variable that `uconst` will read. The second parameter is optional and is the file path of the `.env.*` file to read the environment variable from, e.g. `env!("ENV_VAR", "./.env.prod")`, `"ENV_VAR"` is the key to read the value from and `"./.env.prod"` is the file path relative to [`CARGO_MANIFEST_DIR`]. +/// +/// ```rust +/// use typenum::{U69, assert_type_eq}; +/// use typenum_consts::tnconst; +/// // ``` .env +/// // ENV_VAR=69 +/// // ``` +/// type E = tnconst![env!("ENV_VAR");]; +/// assert_type_eq!(E, U69); +/// ``` +/// +/// It is a compilation error if the environment variable evaluate to a negative (positive) literal integer and you had specify a `+` (`-`) prefix to ask [`tnconst!`] to evaluate the input as a [`typenum::PInt`] ([`typenum::NInt`]). +/// +/// ```compile_fail +/// use typenum::{P69, assert_type_eq}; +/// use typenum_consts::tnconst; +/// // ``` .env +/// // NENV_VAR=-69 +/// // ``` +/// type F = tnconst![+ env!("NENV_VAR");]; +/// assert_type_eq!(F, P69); +/// ``` +/// +/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates +/// [`typenum::Unsigned`]: https://docs.rs/typenum/latest/typenum/marker_traits/trait.Unsigned.html +/// [`typenum::Integer`]: https://docs.rs/typenum/latest/typenum/marker_traits/trait.Integer.html +/// [`typenum::PInt`]: https://docs.rs/typenum/latest/typenum/int/struct.PInt.html +/// [`typenum::UInt`]: https://docs.rs/typenum/latest/typenum/uint/struct.UInt.html +/// [`typenum::NInt`]: https://docs.rs/typenum/latest/typenum/int/struct.NInt.html +/// [`typenum`]: https://github.com/paholg/typenum/tree/main/src #[proc_macro] pub fn tnconst(items: TokenStream) -> TokenStream { match syn::parse::(items) { @@ -51,10 +217,93 @@ pub fn tnconst(items: TokenStream) -> TokenStream { } } +/// [`pconst!`] is a procedural macro that converts a literal integer or an expression into a [`typenum`]'s type-level positive integer (i.e. the type implements the [`typenum::Integer`] trait). +/// +/// There are three ways you can invoke this macro. +/// +/// ## 1. Invoke it with a literal integer +/// +/// ```rust +/// use typenum::{P123, assert_type_eq}; +/// use typenum_consts::pconst; +/// +/// type A = pconst![123]; +/// assert_type_eq!(A, P123); +/// ``` +/// +/// Compilation fails if the literal integer is prefixed with either a `-` or a `+`. +/// +/// ```compile_fail +/// # use typenum::{P123, assert_type_eq}; +/// # use typenum_consts::pconst; +/// type B = pconst![+123]; // Fail to compile +/// ``` +/// +/// ```compile_fail +/// # use typenum::{P123, assert_type_eq}; +/// # use typenum_consts::pconst; +/// type C = pconst![-123]; // Fail to compile +/// ``` +/// +/// ## 2. Invoke using an expression or many simple mathematical expressions +/// +/// ```rust +/// use typenum::{P15, assert_type_eq}; +/// use typenum_consts::pconst; +/// type D = pconst![{ +/// a = 10; +/// b = 5; +/// a + b; // Last statement is always the final returned value to be casted into `typenum` type-level integer, P15 +/// }]; +/// assert_type_eq!(D, P15); +/// ``` +/// +/// It is a compilation error if the mathematical expressions evaluate to a negative literal integer. +/// +/// ```compile_fail +/// use typenum::{P15, assert_type_eq}; +/// use typenum_consts::pconst; +/// type D = pconst![{ +/// a = 10; +/// b = 5; +/// b - a; // 5 - 10 = -5, cannot be made into a `PInt` +/// }]; +/// assert_type_eq!(D, P15); +/// ``` +/// +/// ## 3. Invoke by reading from an environment variable +/// +/// Note: `env!(...)` is a macro-like invocation. The first parameter is mandatory and is the key of the environment variable that `pconst` will read. The second parameter is optional and is the file path of the `.env.*` file to read the environment variable from, e.g. `env!("ENV_VAR", "./.env.prod")`, `"ENV_VAR"` is the key to read the value from and `"./.env.prod"` is the file path relative to [`CARGO_MANIFEST_DIR`]. +/// +/// ```rust +/// use typenum::{P69, assert_type_eq}; +/// use typenum_consts::pconst; +/// // ``` .env +/// // ENV_VAR=69 +/// // ``` +/// type E = pconst![env!("ENV_VAR");]; +/// assert_type_eq!(E, P69); +/// ``` +/// +/// It is a compilation error if the environment variable evaluate to a negative literal integer. +/// +/// ```compile_fail +/// use typenum::{P69, assert_type_eq}; +/// use typenum_consts::pconst; +/// // ``` .env +/// // NENV_VAR=-69 +/// // ``` +/// type F = pconst![env!("NENV_VAR");]; +/// assert_type_eq!(F, P69); +/// ``` +/// +/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates +/// [`typenum::Integer`]: https://docs.rs/typenum/latest/typenum/marker_traits/trait.Integer.html +/// [`typenum`]: https://github.com/paholg/typenum/tree/main/src #[proc_macro] pub fn pconst(items: TokenStream) -> TokenStream { - match syn::parse::(items) { - Ok(litint_exprs) => match litint_exprs { + match syn::parse::(items) { + Ok(litint_exprs) => match litint_exprs.0 { LitIntegerOrExprs::Exprs(math_exprs) => pconst_impl_math_exprs(math_exprs) .unwrap_or_else(syn::Error::into_compile_error) .into(), @@ -66,10 +315,93 @@ pub fn pconst(items: TokenStream) -> TokenStream { } } +/// [`nconst!`] is a procedural macro that converts a literal integer or an expression into a [`typenum`]'s type-level negative integer (i.e. the type implements the [`typenum::Integer`] trait). +/// +/// There are three ways you can invoke this macro. +/// +/// ## 1. Invoke it with a literal integer +/// +/// ```rust +/// use typenum::{N123, assert_type_eq}; +/// use typenum_consts::nconst; +/// +/// type A = nconst![123]; +/// assert_type_eq!(A, N123); +/// ``` +/// +/// Compilation fails if the literal integer is prefixed with either a `-` or a `+`. +/// +/// ```compile_fail +/// # use typenum::{N123, assert_type_eq}; +/// # use typenum_consts::nconst; +/// type B = nconst![+123]; // Fail to compile +/// ``` +/// +/// ```compile_fail +/// # use typenum::{N123, assert_type_eq}; +/// # use typenum_consts::nconst; +/// type C = nconst![-123]; // Fail to compile +/// ``` +/// +/// ## 2. Invoke using an expression or many simple mathematical expressions +/// +/// ```rust +/// use typenum::{N15, assert_type_eq}; +/// use typenum_consts::nconst; +/// type D = nconst![{ +/// a = 10; +/// b = 5; +/// 0 - a - b; // Last statement is always the final returned value to be casted into `typenum` type-level integer, N15 +/// }]; +/// assert_type_eq!(D, N15); +/// ``` +/// +/// It is a compilation error if the mathematical expressions evaluate to a positive literal integer. +/// +/// ```compile_fail +/// use typenum::{N15, assert_type_eq}; +/// use typenum_consts::nconst; +/// type D = nconst![{ +/// a = 10; +/// b = 5; +/// b + a; // 5 + 10 = 15, cannot be made into a `N15` +/// }]; +/// assert_type_eq!(D, N15); +/// ``` +/// +/// ## 3. Invoke by reading from an environment variable +/// +/// Note: `env!(...)` is a macro-like invocation. The first parameter is mandatory and is the key of the environment variable that `nconst` will read. The second parameter is optional and is the file path of the `.env.*` file to read the environment variable from, e.g. `env!("ENV_VAR", "./.env.prod")`, `"ENV_VAR"` is the key to read the value from and `"./.env.prod"` is the file path relative to [`CARGO_MANIFEST_DIR`]. +/// +/// ```rust +/// use typenum::{N69, assert_type_eq}; +/// use typenum_consts::nconst; +/// // ``` .env +/// // NENV_VAR=-69 +/// // ``` +/// type E = nconst![env!("NENV_VAR");]; +/// assert_type_eq!(E, N69); +/// ``` +/// +/// It is a compilation error if the environment variable evaluate to a positive literal integer. +/// +/// ```compile_fail +/// use typenum::{N69, assert_type_eq}; +/// use typenum_consts::nconst; +/// // ``` .env +/// // ENV_VAR=69 +/// // ``` +/// type F = nconst![env!("ENV_VAR");]; +/// assert_type_eq!(F, N69); +/// ``` +/// +/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates +/// [`typenum::Integer`]: https://docs.rs/typenum/latest/typenum/marker_traits/trait.Integer.html +/// [`typenum`]: https://github.com/paholg/typenum/tree/main/src #[proc_macro] pub fn nconst(items: TokenStream) -> TokenStream { - match syn::parse::(items) { - Ok(litint_exprs) => match litint_exprs { + match syn::parse::(items) { + Ok(litint_exprs) => match litint_exprs.0 { LitIntegerOrExprs::Exprs(math_exprs) => nconst_impl_math_exprs(math_exprs) .unwrap_or_else(syn::Error::into_compile_error) .into(), diff --git a/src/macros.rs b/src/macros.rs index d5ad969..258986c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -2,7 +2,7 @@ macro_rules! debug_eprintln { ($($arg: tt)*) => { #[cfg(debug_assertions)] - #[cfg(feature = "debug")] + #[cfg(__debug_tnconst)] ::std::eprintln!($($arg)*) }; } diff --git a/src/tnconst_impl.rs b/src/tnconst_impl.rs index 6e54dcf..c2d38b0 100644 --- a/src/tnconst_impl.rs +++ b/src/tnconst_impl.rs @@ -36,7 +36,9 @@ pub(crate) fn nconst_impl_math_exprs(math_exprs: MathExprs) -> Result Result { match lit_integer { - LitInteger::Unsigned { lit_integer } => nconst_impl(lit_integer), + LitInteger::Unsigned { lit_integer } => Err( + Error::new(lit_integer.span(), "using `nconst![...]` but the inputs to the macro results in an unsigned literal integer") + ), LitInteger::Positive { lit_integer } => Err( Error::new(lit_integer.span(), "using `nconst![...]` but the inputs to the macro results in an positive literal integer") ), @@ -69,6 +71,11 @@ pub(crate) fn pconst_impl(lit_integer: LitInt) -> Result { } pub(crate) fn nconst_impl(lit_integer: LitInt) -> Result { + let mut lit_integer_ = lit_integer.base10_parse::()?; + if lit_integer_ < 0 { + lit_integer_ *= -1; + } + let lit_integer = LitInt::new(format!("{}", lit_integer_).as_str(), lit_integer.span()); let unsigned_ts = uconst_impl::uconst_impl(lit_integer)?; Ok(quote!( ::typenum::NInt<#unsigned_ts> diff --git a/src/vendors/mod.rs b/src/vendors/mod.rs new file mode 100644 index 0000000..ee3ecd2 --- /dev/null +++ b/src/vendors/mod.rs @@ -0,0 +1 @@ +pub(crate) mod rsc; diff --git a/src/vendors/rsc/.gitignore b/src/vendors/rsc/.gitignore new file mode 100644 index 0000000..16ff01e --- /dev/null +++ b/src/vendors/rsc/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +/.vs +/.idea diff --git a/src/vendors/rsc/CHANGELOG.md b/src/vendors/rsc/CHANGELOG.md new file mode 100644 index 0000000..1f47b61 --- /dev/null +++ b/src/vendors/rsc/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 3.0 - 2021-08-30 +### Additions +#### In the executable + * Help command list + * `vars` command shows active variables and functions. + * BigRational from num crate replaces f64. + +### Changed + * Rewrote *everything*. + * Lexer and `Token` architecture. `Token` now includes data about where it was located in the input, and how many characters it spans, which is useful for errors. + * Parser and `Expr` usage. Overall code cleanup for the parser. Planning to rewrite the parser using an Operator-precedence approach. Gotta study up before I can write a bottom-up parser from scratch. Now uses a lookahead of 2 to solve ambiguity in parsing. See grammar. + * `ParseError` is now descriptive, including the position and length of the problem, and even sometimes providing the offending token. + * `Computer` became `Interpreter`. A lot of changes were made to the interpreter, compared to the old `Computer` that you should check out when migrating. + * Some semantic expressions like absolute value `|x|` and factorial `x!` are now translated to `abs(x)` and `factorial(x)`, respectively. + * The entire system still remains generic over which type of number is used, but I have simplified and extended the trait `Num` which a type must still implement to be used. + +### Removed + * `ans` variable. + * Global `eval` function and `EvalError` tagged enum. The "simplistic" interface was really quite complex and made things pretty complicated. + +### Fixed + * Some bugs in the grammar that caused seemingly ordinary expressions to produce false results. + * Determining at runtime whether `x(5)` is a function `x` with an argument `5` or a variable `x` times `5`. + * Functions were accidentally defined as the trait `Fn`, embarrassingly. I've updated functions, so they are now actually usable. + +## 2.0 - 2019-06-21 +### Added +* Real named functions! Functions are no longer tokens, and can now be created in a `Computer`, similar to variables. +```rust +let mut map = HashMap:: f64>::new(); +map.insert("sqrt".to_owned(), &|n| n.sqrt()); +``` +* RSC is fully generic, now! Types that can support addition, subtraction, and a couple functions necessary in the `Num` trait can be lexed, parsed, and computed with no changes to the RSC source code. +* Getting the previous answer with the new `ans` variable. `ans` does not exist until you've run a calculation on a Computer already. +* Factorial: `5! = 120` + +## [1.2.1] - 2017-06-20 +### Removed +* Tests from lib.rs removed so it can compile on stable compiler branches. + +*Versions prior to 1.2.1 had no changelog recordings, unfortunately.* diff --git a/src/vendors/rsc/Cargo.toml.vendored b/src/vendors/rsc/Cargo.toml.vendored new file mode 100644 index 0000000..0c7af01 --- /dev/null +++ b/src/vendors/rsc/Cargo.toml.vendored @@ -0,0 +1,29 @@ +[package] +name = "rsc" +version = "3.0.0" +edition = "2018" +authors = ["Luke I. Wilson "] +description = "A fast calculator for solving scientific and algebraic math equations in strings." +repository = "https://github.com/fivemoreminix/rsc" +readme = "README.md" +keywords = ["scientific", "calculator", "parser", "expression"] +categories = ["command-line-utilities", "parsing", "science"] +license = "MIT" + +[lib] +path = "src/lib.rs" + +[[bin]] +name = "rsc" +path = "src/bin/main.rs" +required-features = ["executable"] + +[features] +executable = ["structopt", "colored"] + +[dependencies] +peekmore = "1.0.0" +#num = "0.4.0" +# dependencies for the runnable version (feature "executable") +structopt = { version = "0.3", optional = true } +colored = { version = "2.0", optional = true } diff --git a/src/vendors/rsc/LICENSE b/src/vendors/rsc/LICENSE new file mode 100644 index 0000000..f4b847c --- /dev/null +++ b/src/vendors/rsc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Luke Wilson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/vendors/rsc/README.md b/src/vendors/rsc/README.md new file mode 100644 index 0000000..7e8a4aa --- /dev/null +++ b/src/vendors/rsc/README.md @@ -0,0 +1,140 @@ +RSC, the Calculator for Rust Code +================================= +![](https://img.shields.io/crates/l/rsc.svg) ![](https://img.shields.io/badge/status-stable-blue.svg) + +**New**: crate updated to 3.0, read the [Changelog](CHANGELOG.md). + +**RSC is a handwritten scientific calculator for interpreting equations inside strings.** RSC is designed to do a single +thing very well, enabling anyone to extend it with more features. + +RSC intends to beat Wirth's Law. **Therefore, RSC will not receive many additions.** It will still receive updates with +relation to efficiency. + +## Library +```rust +use rsc::{tokenize, parse, Interpreter}; + +// Maybe you write a wrapper function +fn evaluate(input: &str, interpreter: &mut Interpreter) -> Result { + // You have to call each function in the pipeline, but this gives you the most + // control over error handling and performance. + match tokenize(input) { // Step 1: splits input into symbols, words, and numbers + Ok(tokens) => match parse(&tokens) { // Step 2: builds an Expr using tokens + Ok(expr) => match interpreter.eval(&expr) { // Step 3: interprets the Expr + Ok(result) => println!("{}", result), + Err(interpret_error) => eprintln!("{:?}", interpret_error), + }, + Err(parse_error) => eprintln!("{:?}", parse_error), + }, + Err(tokenize_error) => eprintln!("{:?}", tokenize_error), + } +} + +fn main() { + // Constructs an f64 interpreter with included variables + let mut interpreter = Interpreter::default(); + + evaluate("5^2", &mut interpreter); // prints "25" + evaluate("x = 3", &mut interpreter); // prints "3" + evaluate("x(3) + 1", &mut interpreter); // prints "10" +} +``` + +Variables are stored in the `Interpreter`: +```rust +use rsc::{tokenize, parse, Interpreter, Variant, InterpretError}; + +// assume you still had your evaluate function above + +fn main() { + // Create a completely empty interpreter for f64 calculations + let mut i = Interpreter::::new(); + + // Create some variables + i.set_var(String::from("pi"), Variant::Num(std::f64::consts::PI)); + i.set_var(String::from("double"), Variant::Function(|name, args| { + if args.len() < 1 { + Err(InterpretError::TooFewArgs(name, 1)) + } else if args.len() > 1 { + Err(InterpretError::TooManyArgs(name, 1)) + } else { + Ok(args[0] * 2) // get the only argument and double it + } + })); + + evaluate("double(pi)", &mut i); // prints "6.283185307179586" +} +``` + +Because it can be redundant checking that functions received the correct number of arguments (if you wish to do so at all), +I made a helper function called `ensure_arg_count`. The above function redefined: + +```rust +use rsc::ensure_arg_count; + +i.set_var(String::from("double"), Variant::Function(|name, args| { + // return Err if args are not within the min and max count + ensure_arg_count(1, 1, args.len(), name)?; + Ok(args[0] * 2) +})); +``` + +## Executable +### First you might need to build RSC as an executable +```shell +cargo build --release --features=executable +``` +The `executable` feature is required to tell the crate to bring in certain dependencies only for the executable version. + +### Usage +```shell +RSC interactive expression interpreter. +Try "help" for commands and examples. +>sqrt(15+3) +:4.242640687119285 +>:square root +>sqrt(15, 3) +Function "sqrt" received more than the maximum 1 argument. +> |-5| +:5 +>abs(-5) +:5 +>sqrt(4)(2) + ^ UnexpectedToken(Token { value: Symbol(LP), span: 7..8 }) +>(sqrt(4))(2) +:4 +>x = 1.24 +:1.24 +>x(4) +:4.96 +>vars +factorial(..) +sqrt(..) +abs(..) +x = 1.24 +e = 2.718281828459045 +pi = 3.141592653589793 +tau = 6.283185307179586 +``` + +Expressions can be passed to rsc directly: +```shell +rsc "12/sqrt(128)" > result.txt +``` + +There are various flags you can pass. Try: +```shell +rsc -tev +``` + +## Notes About Performance + * The lexer is iterative but could easily be optimized. + * The parser is an LL(2) recursive-descent parser, and that's the simplest, most brute-force parsing solution I came up with. But, I plan to replace it with an LR(2) operator-precedence parser, which would be much more efficient. The parser is currently the slowest of the 3 phases. + * The `Interpreter::eval` function uses recursion for simplicity. Removing the recursion could prevent unnecessary pushing and popping of the frame pointer, providing better performance where recursive calls are found. + * Performance improvement PRs are very much welcomed and probably easy! + +## Stability +RSC will not have any major changes to its syntax. It will remain consistent for a long time. It is up to forkers to make different tastes of RSC. It will also forever keep the same open-source permissions. + +## License +RSC is MIT licensed. RSC will always remain free to modify and use without attribution. diff --git a/src/vendors/rsc/benches/benches.rs b/src/vendors/rsc/benches/benches.rs new file mode 100644 index 0000000..3048b66 --- /dev/null +++ b/src/vendors/rsc/benches/benches.rs @@ -0,0 +1,59 @@ +#![feature(test)] + +extern crate test; + +use rsc::{parse, tokenize, Interpreter, Variant}; +use test::{black_box, Bencher}; + +const SHORT_STR: &str = "5.324 * 54(pad)"; +const LONG_STR: &str = "0.999998543 / sqrt(54 ^ (x(3))) % applesauce + bees"; +const FUNCTIONS_VARS: &str = "abs(5) + x(3) + abs(x(2)) + sqrt(4)"; + +macro_rules! tokenizer_bench { + ($name:ident, $input:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + b.iter(|| tokenize::(black_box($input))); + } + }; +} + +macro_rules! parser_bench { + ($name:ident, $input:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let tokens = tokenize::($input).unwrap(); + b.iter(|| parse(black_box(&tokens))) + } + }; +} + +macro_rules! eval_bench { + ($name:ident, $input:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let tokens = tokenize($input).unwrap(); + let expr = parse(&tokens).unwrap(); + let mut i = Interpreter::default(); + i.set_var(String::from("pad"), Variant::Num(5.0)); + i.set_var(String::from("x"), Variant::Num(2.0)); + i.set_var(String::from("applesauce"), Variant::Num(1.0)); + i.set_var(String::from("bees"), Variant::Num(1.0)); + b.iter(|| { + i.eval(black_box(&expr)).unwrap(); + }) + } + }; +} + +tokenizer_bench!(tokenizer_short_expr, SHORT_STR); +tokenizer_bench!(tokenizer_long_expr, LONG_STR); +tokenizer_bench!(tokenizer_function_vars, FUNCTIONS_VARS); + +parser_bench!(parser_short_expr, SHORT_STR); +parser_bench!(parser_long_expr, LONG_STR); +parser_bench!(parser_function_vars, FUNCTIONS_VARS); + +eval_bench!(eval_short_expr, SHORT_STR); +eval_bench!(eval_long_expr, LONG_STR); +eval_bench!(eval_function_vars, FUNCTIONS_VARS); diff --git a/src/vendors/rsc/grammar b/src/vendors/rsc/grammar new file mode 100644 index 0000000..eb236f9 --- /dev/null +++ b/src/vendors/rsc/grammar @@ -0,0 +1,23 @@ +expr = eq_expr ; + +eq_expr = add_expr, { "=", add_expr } ; +add_expr = mul_expr, { ("+" | "-"), mul_expr } ; +mul_expr = pow_expr, { ("*" | "/" | "%"), pow_expr } ; +pow_expr = parentheses_mul_expr, { "^", factor } ; + +parentheses_mul_expr = func_or_var_mul_expr | ( factorial_expr, { "(", expr, ")" } ) ; +func_or_var_mul_expr = identifier, "(", [ expr { ",", expr } ], ")" ; (* need lookahead 2 *) + +factorial_expr = factor, { "!" } ; +factor = "(", expr, ")" + | "|", expr, "|" + | "-", expr + | number + | identifier ; + +identifier = alpha, { alpha | digit } ; +alpha = "A".."Z" | "a".."z" ; + +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +(* number = 52 or .14 or -65535 or -.256 or -340.430 etc *) +number = [ "-" ], ( digit, { digit }, [ ".", { digit } ] ) | ( ".", digit, { digit } ) ; diff --git a/src/vendors/rsc/mod.rs b/src/vendors/rsc/mod.rs new file mode 100644 index 0000000..58fd1a9 --- /dev/null +++ b/src/vendors/rsc/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod src; +// 'reexport src::mod;' +pub(crate) use src::*; diff --git a/src/vendors/rsc/src/bin/main.rs b/src/vendors/rsc/src/bin/main.rs new file mode 100644 index 0000000..09b6a0b --- /dev/null +++ b/src/vendors/rsc/src/bin/main.rs @@ -0,0 +1,284 @@ +use colored::Colorize; +use std::io::prelude::*; +use structopt::StructOpt; + +use rsc::{ + parse, tokenize, InterpretError, Interpreter, Num, ParseError, ParseErrorCode, TokenizeError, + Variant, +}; +use std::fmt::Display; +use std::ops::Range; + +#[derive(StructOpt)] +#[structopt(about = "A scientific calculator for the terminal.")] +struct Opt { + #[structopt()] + expr: Option, + #[structopt(short = "t", long = "tokens", help = "Prints the tokens")] + tokens: bool, + #[structopt(short = "e", long = "expr", help = "Prints the expression tree")] + bexpr: bool, + #[structopt(short = "v", long = "vars", help = "Prints variable map")] + vars: bool, + #[structopt(long = "no-color", help = "Prevents colored text")] + no_color: bool, +} + +fn main() { + let opt = Opt::from_args(); + + let mut interpreter = Interpreter::default(); + + if let Some(expr) = opt.expr { + match tokenize(&expr) { + Ok(tokens) => match parse(&tokens) { + Ok(expr) => match interpreter.eval(&expr) { + Ok(result) => { + println!("{}", result); + return; + } + Err(e) => eprintln!("{:?}", e), + }, + Err(ParseError { code, span }) => eprintln!("{:?} at {:?}", code, span), + }, + Err(TokenizeError { code, span }) => eprintln!("{:?} at {:?}", code, span), + } + std::process::exit(1); + } + + println!("RSC interactive expression interpreter."); + println!("Try \"help\" for commands and examples."); + + loop { + print!( + "{}", + if opt.no_color { + ">".normal() + } else { + ">".blue() + } + ); + std::io::stdout().flush().unwrap(); + + let mut buffer = String::new(); + std::io::stdin().read_line(&mut buffer).unwrap(); + buffer = buffer.trim().to_owned(); + + if &buffer[..] == "quit" || &buffer[..] == "exit" { + break; + } else if &buffer[..] == "help" { + print_help(opt.no_color); + } else if &buffer[..] == "vars" { + print_vars(&interpreter, opt.no_color); + } else if &buffer[..] == "clear" { + for _ in 0..100 { + println!(); + } + continue; + } else if buffer.starts_with(":") { + continue; + } else { + evaluate( + &buffer, + &mut interpreter, + opt.tokens, + opt.bexpr, + opt.vars, + opt.no_color, + ":", + ); + } + } +} + +const COMMANDS: [(&str, &str); 5] = [ + ("quit|exit", "Close RSC"), + ("help", "Show this help information"), + ("vars", "Display all of the active variables"), + ("clear", "Clear prior output"), + (":", "Write notes"), +]; + +fn print_help(no_color: bool) { + println!("Commands"); + for (name, desc) in COMMANDS { + println!( + "{:<10} {}", + if no_color { + name.green().clear() + } else { + name.green() + }, + desc + ); + } + println!("\nExamples"); + println!("\t12.3(0.7)"); + println!("\t|-9| + 3!"); + println!("\tx = abs(5)"); + println!("\t-x^4"); +} + +fn get_variant_ord(v: &Variant) -> usize { + match v { + Variant::Num(_) => 1, + Variant::Function(_) => 0, + } +} + +fn print_vars(interpreter: &Interpreter, no_color: bool) { + let mut vars: Vec<(&String, &Variant)> = interpreter.vars.iter().collect(); + vars.sort_by(|(_, v1), (_, v2)| { + // sort by type + let v1_val = get_variant_ord(v1); + let v2_val = get_variant_ord(v2); + v1_val.cmp(&v2_val) + }); + for (id, val) in vars { + let fmt; + match val { + Variant::Num(n) => fmt = format!("{} = {}", &id.green(), n.clone()), + Variant::Function(_) => fmt = format!("{}(..)", &id.green()), + } + println!( + "{}", + if no_color { + fmt.red().clear().to_string() + } else { + fmt + } + ); + } +} + +fn format_error(span: Range, message: &str) -> String { + format!( + " {}{} {}", + " ".repeat(span.start), + "^".repeat(span.len()).red(), + message.red() + ) +} + +fn evaluate( + input: &str, + interpreter: &mut Interpreter, + btokens: bool, + bexpr: bool, + bvars: bool, + bno_color: bool, + success_prefix: &str, +) { + match tokenize(input) { + Ok(tokens) => { + if btokens { + let fmt = format!("Tokens: {:?}", tokens); + println!( + "{}", + if bno_color { + fmt + } else { + fmt.yellow().to_string() + } + ); + } + match parse(&tokens) { + Ok(expr) => { + if bexpr { + let fmt = format!("Expr: {:#?}", expr); + println!( + "{}", + if bno_color { + fmt + } else { + fmt.yellow().to_string() + } + ); + } + + match interpreter.eval(&expr) { + Ok(result) => { + println!("{}{}", success_prefix, result); + } + Err(err) => { + let fmt = format!("{}", display_interpret_error(&err)); + println!( + "{}", + if bno_color { + fmt + } else { + fmt.red().to_string() + } + ); + } + } + } + Err(ParseError { code, span }) => { + if code == ParseErrorCode::UnexpectedEOF { + println!( + "{}", + format_error(input.len()..input.len() + 1, &format!("{:?}", code)) + ); + } else { + println!("{}", format_error(span, &format!("{:?}", code))); + } + } + } + } + Err(TokenizeError { code, span }) => { + println!("{}", format_error(span, &format!("{:?}", code))); + } + } + if bvars { + for (id, variant) in &interpreter.vars { + let fmt; + if let Variant::Num(n) = variant { + fmt = format!("{} = {}", id, n); + } else { + fmt = format!("{}(..)", id); + } + println!( + "{}", + if bno_color { + fmt + } else { + fmt.yellow().to_string() + } + ); + } + } +} + +#[inline(always)] +fn s_if(b: bool) -> &'static str { + if b { + "s" + } else { + "" + } +} + +fn display_interpret_error(err: &InterpretError) -> String { + match err { + InterpretError::TooFewArgs(id, n) => format!( + "Function {:?} did not receive minimum of {} argument{}.", + id, + n, + s_if(*n != 1) + ), + InterpretError::TooManyArgs(id, n) => format!( + "Function {:?} received more than the maximum {} argument{}.", + id, + n, + s_if(*n != 1) + ), + InterpretError::VarDoesNotExist(id) => format!("No variable or function {:?} exists.", id), + InterpretError::VarIsNotFunction(id) => format!( + "The variable {:?} cannot be used like a function with arguments.", + id + ), + InterpretError::FunctionNameUsedLikeVar(id) => { + format!("The function {:?} cannot be used without arguments.", id) + } + } +} diff --git a/src/vendors/rsc/src/expr.rs b/src/vendors/rsc/src/expr.rs new file mode 100644 index 0000000..d15a3be --- /dev/null +++ b/src/vendors/rsc/src/expr.rs @@ -0,0 +1,11 @@ +use crate::{rsc::Num, rsc::OpVal}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Expr<'input, N: Num> { + Eq(Box>, Box>), + FuncOrVarMul(&'input str, Vec>), + Neg(Box>), + Num(&'input N), + Op(OpVal, Box>, Box>), + Var(&'input str), +} diff --git a/src/vendors/rsc/src/interpreter.rs b/src/vendors/rsc/src/interpreter.rs new file mode 100644 index 0000000..dcc8042 --- /dev/null +++ b/src/vendors/rsc/src/interpreter.rs @@ -0,0 +1,156 @@ +use crate::rsc::{Expr, Num, OpVal}; +use std::collections::HashMap; +use std::ops::Deref; + +#[derive(Clone)] +pub enum Variant { + Num(N), + Function(for<'expr> fn(&'expr str, &[N]) -> Result>), +} + +#[derive(Debug, Clone)] +pub enum InterpretError<'expr> { + TooFewArgs(&'expr str, usize), // Id of function, min args + TooManyArgs(&'expr str, usize), // Id of function, max args + VarDoesNotExist(&'expr str), + VarIsNotFunction(&'expr str), + FunctionNameUsedLikeVar(&'expr str), +} + +#[derive(Clone)] +pub struct Interpreter { + pub vars: HashMap>, +} + +impl Interpreter { + #[inline(always)] + pub fn new() -> Interpreter { + Interpreter { + vars: HashMap::new(), + } + } + + #[inline(always)] + pub fn set_var(&mut self, name: String, value: Variant) { + self.vars.insert(name, value); + } + + pub fn eval<'expr>(&mut self, expr: &'expr Expr) -> Result> { + // simple, naive recursive tree walk + match expr { + Expr::Eq(lhs, rhs) => match lhs.deref() { + Expr::Var(id) => { + let result = self.eval(rhs)?; + if let Some(val) = self.vars.get_mut(*id) { + *val = Variant::Num(result.clone()); + } else { + self.vars + .insert(id.to_string(), Variant::Num(result.clone())); + } + Ok(result) + } + _ => todo!("implement algebra solving"), + }, + Expr::FuncOrVarMul(id, exprs) => { + let mut args = Vec::with_capacity(exprs.len()); + for expr in exprs { + args.push(self.eval(expr)?); + } + + if let Some(var) = self.vars.get(*id) { + match var { + Variant::Num(n) => { + if args.len() == 1 { + let arg = args.remove(0); + Ok(n.clone().mul(arg)) + } else { + Err(InterpretError::VarIsNotFunction(id)) + } + } + Variant::Function(func) => func(id, &args), + } + } else { + Err(InterpretError::VarDoesNotExist(id)) + } + } + Expr::Neg(expr) => Ok(-self.eval(expr)?), + #[allow(suspicious_double_ref_op)] + Expr::Num(n) => Ok(n.deref().clone()), + Expr::Op(op, lhs, rhs) => { + let lhs = self.eval(lhs)?; + let rhs = self.eval(rhs)?; + Ok(match op { + OpVal::Add => lhs + rhs, + OpVal::Sub => lhs - rhs, + OpVal::Mul => lhs * rhs, + OpVal::Div => lhs / rhs, + OpVal::Mod => lhs % rhs, + OpVal::Pow => lhs.pow(rhs), + _ => unreachable!(), + }) + } + Expr::Var(id) => { + if let Some(var) = self.vars.get(*id) { + match var { + Variant::Num(n) => Ok(n.clone()), + Variant::Function(_) => Err(InterpretError::FunctionNameUsedLikeVar(id)), + } + } else { + Err(InterpretError::VarDoesNotExist(id)) + } + } + } + } +} + +#[inline] +pub fn ensure_arg_count( + min: usize, + max: usize, + args_len: usize, + func_id: &str, +) -> Result<(), InterpretError> { + if args_len < min { + Err(InterpretError::TooFewArgs(func_id, min)) + } else if args_len > max { + Err(InterpretError::TooManyArgs(func_id, max)) + } else { + Ok(()) + } +} + +impl Default for Interpreter { + fn default() -> Self { + let mut vars = HashMap::new(); + vars.insert(String::from("pi"), Variant::Num(std::f64::consts::PI)); + vars.insert(String::from("e"), Variant::Num(std::f64::consts::E)); + vars.insert(String::from("tau"), Variant::Num(std::f64::consts::TAU)); + vars.insert( + String::from("abs"), + Variant::Function(|id, args| { + ensure_arg_count(1, 1, args.len(), id)?; + Ok(args[0].abs()) + }), + ); + vars.insert( + String::from("sqrt"), + Variant::Function(|id, args| { + ensure_arg_count(1, 1, args.len(), id)?; + Ok(args[0].sqrt()) + }), + ); + vars.insert( + String::from("factorial"), + Variant::Function(|id, args| { + ensure_arg_count(1, 1, args.len(), id)?; + let n = args[0]; + if n <= 1.0 { + Ok(1.0) + } else { + Ok((1..=n as u64).product::() as f64) + } + }), + ); + Interpreter { vars } + } +} diff --git a/src/vendors/rsc/src/mod.rs b/src/vendors/rsc/src/mod.rs new file mode 100644 index 0000000..e87278b --- /dev/null +++ b/src/vendors/rsc/src/mod.rs @@ -0,0 +1,99 @@ +pub(crate) mod expr; +pub(crate) mod interpreter; +pub(crate) mod parser; +pub(crate) mod tokenizer; + +pub(crate) use expr::*; +pub(crate) use interpreter::*; +pub(crate) use parser::*; +pub(crate) use tokenizer::*; + +use std::convert::TryInto; +use std::fmt::Debug; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; +use std::str::FromStr; + +pub trait Num: + Debug + + Clone + + PartialEq + + PartialOrd + + FromStr + + Add + + Sub + + Mul + + Div + + Rem + + Neg + + AddAssign + + SubAssign + + MulAssign + + DivAssign +{ + fn zero() -> Self; + fn one() -> Self; + fn is_whole(&self) -> bool; + fn pow(self, other: Self) -> Self; +} + +// Default impls for Num +impl Num for f32 { + #[inline(always)] + fn zero() -> Self { + 0.0 + } + #[inline(always)] + fn one() -> Self { + 1.0 + } + #[inline(always)] + fn is_whole(&self) -> bool { + self.fract() == 0.0 + } + #[inline(always)] + fn pow(self, other: Self) -> Self { + self.powf(other) + } +} + +impl Num for f64 { + #[inline(always)] + fn zero() -> Self { + 0.0 + } + #[inline(always)] + fn one() -> Self { + 1.0 + } + #[inline(always)] + fn is_whole(&self) -> bool { + self.fract() == 0.0 + } + #[inline(always)] + fn pow(self, other: Self) -> Self { + self.powf(other) + } +} + +impl Num for isize { + #[inline(always)] + fn zero() -> Self { + Self::MIN + } + #[inline(always)] + fn one() -> Self { + 1 + } + #[inline(always)] + fn is_whole(&self) -> bool { + true + } + #[inline(always)] + fn pow(self, other: Self) -> Self { + self.pow( + other + .try_into() + .expect("`isize` failed to convert to `u32`"), + ) + } +} diff --git a/src/vendors/rsc/src/parser.rs b/src/vendors/rsc/src/parser.rs new file mode 100644 index 0000000..4bec2bb --- /dev/null +++ b/src/vendors/rsc/src/parser.rs @@ -0,0 +1,251 @@ +use crate::rsc::{Expr, Num, OpVal, SymbolVal, Token, TokenValue}; +use peekmore::{PeekMore, PeekMoreIterator}; +use std::ops::Range; +use std::slice::Iter; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ParseErrorCode<'t, N: Num> { + ExpectedClosingParen, + UnexpectedToken(&'t Token<'t, N>), + UnexpectedEOF, +} +use ParseErrorCode::*; + +#[derive(Debug, Clone)] +pub struct ParseError<'t, N: Num> { + pub code: ParseErrorCode<'t, N>, + pub span: Range, +} + +pub type ParseResult<'input, N> = Result, ParseError<'input, N>>; + +macro_rules! error { + ($code:expr, $span:expr) => { + ParseError { + code: $code, + span: $span, + } + }; +} + +type TokenIter<'t, N> = PeekMoreIterator>>; + +#[inline] +pub fn parse<'input, N: Num>(tokens: &'input [Token<'input, N>]) -> ParseResult<'input, N> { + let mut iter = tokens.iter().peekmore(); + let result = parse_expr(&mut iter); + match result { + Ok(_) => { + if let Some(tok) = iter.next() { + Err(error!(UnexpectedToken(tok), tok.span.clone())) + } else { + result + } + } + Err(_) => result, + } +} + +#[inline(always)] +fn parse_expr<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + parse_eq(tokens) +} + +fn parse_eq<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + let mut result = parse_add(tokens)?; + while let Some(peek_tok) = tokens.peek() { + if peek_tok.value == TokenValue::Op(OpVal::Eq) { + tokens.next(); // Consume '=' + let rhs = parse_add(tokens)?; + result = Expr::Eq(Box::new(result), Box::new(rhs)); + } else { + break; + } + } + Ok(result) +} + +fn parse_add<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + let mut result = parse_mul(tokens)?; + while let Some(peek_tok) = tokens.peek() { + match peek_tok.value { + TokenValue::Op(op) if op == OpVal::Add || op == OpVal::Sub => { + tokens.next(); // Consume '+' or '-' + let rhs = parse_mul(tokens)?; + result = Expr::Op(op, Box::new(result), Box::new(rhs)); + } + _ => break, + } + } + Ok(result) +} + +fn parse_mul<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + let mut result = parse_pow(tokens)?; + while let Some(peek_tok) = tokens.peek() { + match peek_tok.value { + TokenValue::Op(op) if op == OpVal::Mul || op == OpVal::Div || op == OpVal::Mod => { + tokens.next(); // Consume '*' or '/' or '%' + let rhs = parse_pow(tokens)?; + result = Expr::Op(op, Box::new(result), Box::new(rhs)); + } + _ => break, + } + } + Ok(result) +} + +fn parse_pow<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + let mut result = parse_parentheses_mul(tokens)?; + while let Some(peek_tok) = tokens.peek() { + if peek_tok.value == TokenValue::Op(OpVal::Pow) { + tokens.next(); // Consume '^' + let rhs = parse_factor(tokens)?; + result = Expr::Op(OpVal::Pow, Box::new(result), Box::new(rhs)); + } else { + break; + } + } + Ok(result) +} + +fn parse_parentheses_mul<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + if let Some(func_or_var_mul) = parse_func_or_var_mul(tokens) { + Ok(func_or_var_mul?) + } else { + let mut result = parse_factorial(tokens)?; + while let Some(peek_tok) = tokens.peek() { + if peek_tok.value == TokenValue::Symbol(SymbolVal::LP) { + tokens.next(); // Consume '(' + let rhs = parse_expr(tokens)?; + if let Some(tok) = tokens.next() { + if tok.value == TokenValue::Symbol(SymbolVal::RP) { + result = Expr::Op(OpVal::Mul, Box::new(result), Box::new(rhs)); + } else { + return Err(error!(ExpectedClosingParen, tok.span.clone())); + } + } else { + return Err(error!(UnexpectedEOF, 0..0)); + } + } else { + break; + } + } + Ok(result) + } +} + +// This function returns Option to the result, because it doesn't *have* to parse a value. +// And because it should only be used by parse_parentheses_mul. +#[inline] +fn parse_func_or_var_mul<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> Option> { + match tokens.peek() { + Some(Token { + value: TokenValue::Id(id), + .. + }) => { + // Check for opening parentheses + if let Some(tok) = tokens.peek_nth(1) { + if tok.value != TokenValue::Symbol(SymbolVal::LP) { + return None; + } + } else { + return None; + } + + // Consume previous tokens + tokens.next(); // id + tokens.next(); // '(' + + // At this point, the function should always return a Some(...) + + // Shortcut: function has no parameters + if let Some(tok) = tokens.peek() { + if tok.value == TokenValue::Symbol(SymbolVal::RP) { + tokens.next(); // Consume ')' + return Some(Ok(Expr::FuncOrVarMul(id, Vec::new()))); + } + } + + // Collecting function parameters + let mut params = Vec::with_capacity(3); + while let Ok(expr) = parse_expr(tokens) { + params.push(expr); + match tokens.next() { + Some(Token { + value: TokenValue::Symbol(SymbolVal::Comma), + .. + }) => { + continue; + } + Some(Token { + value: TokenValue::Symbol(SymbolVal::RP), + .. + }) => { + break; + } + Some(tok) => return Some(Err(error!(UnexpectedToken(tok), tok.span.clone()))), + None => return Some(Err(error!(UnexpectedEOF, 0..0))), + } + } + Some(Ok(Expr::FuncOrVarMul(id, params))) + } + _ => None, + } +} + +fn parse_factorial<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + let mut result = parse_factor(tokens)?; + while let Some(peek_tok) = tokens.peek() { + if peek_tok.value == TokenValue::Op(OpVal::Exclaim) { + tokens.next(); // Consume '!' + result = Expr::FuncOrVarMul("factorial", vec![result]); + } else { + break; + } + } + Ok(result) +} + +fn parse_factor<'t, N: Num>(tokens: &mut TokenIter<'t, N>) -> ParseResult<'t, N> { + match tokens.next() { + Some(tok) => match &tok.value { + TokenValue::Num(num) => Ok(Expr::Num(num)), + TokenValue::Id(id) => Ok(Expr::Var(id)), + TokenValue::Op(op) => match op { + OpVal::Sub => Ok(Expr::Neg(Box::new(parse_expr(tokens)?))), + _ => Err(error!(UnexpectedToken(tok), tok.span.clone())), + }, + TokenValue::Symbol(sym) => match sym { + SymbolVal::LP => { + let expr = parse_expr(tokens)?; + // Expect a closing parentheses + if let Some(tok) = tokens.next() { + if tok.value == TokenValue::Symbol(SymbolVal::RP) { + Ok(expr) + } else { + Err(error!(UnexpectedToken(tok), tok.span.clone())) + } + } else { + Err(error!(UnexpectedEOF, 0..0)) + } + } + SymbolVal::Pipe => { + let expr = parse_expr(tokens)?; + // Expect a closing pipe + if let Some(tok) = tokens.next() { + if tok.value == TokenValue::Symbol(SymbolVal::Pipe) { + Ok(Expr::FuncOrVarMul("abs", vec![expr])) + } else { + Err(error!(UnexpectedToken(tok), tok.span.clone())) + } + } else { + Err(error!(UnexpectedEOF, 0..0)) + } + } + _ => Err(error!(UnexpectedToken(tok), tok.span.clone())), + }, + }, + None => Err(error!(UnexpectedEOF, 0..0)), + } +} diff --git a/src/vendors/rsc/src/tokenizer.rs b/src/vendors/rsc/src/tokenizer.rs new file mode 100644 index 0000000..c90dc8f --- /dev/null +++ b/src/vendors/rsc/src/tokenizer.rs @@ -0,0 +1,144 @@ +use crate::rsc::Num; +use std::ops::Range; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum OpVal { + Add, + Sub, + Mul, + Div, + Mod, + Pow, + Eq, + Exclaim, +} +use OpVal::*; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SymbolVal { + LP, + RP, + Comma, + Pipe, +} +use SymbolVal::*; + +#[derive(Debug, Clone, PartialEq)] +pub enum TokenValue<'input, N: Num> { + Num(N), + Id(&'input str), + Op(OpVal), + Symbol(SymbolVal), +} +use TokenValue::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Token<'input, N: Num> { + pub value: TokenValue<'input, N>, + pub span: Range, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TokenizeErrorCode<'input> { + InvalidNumber(&'input str), + UnrecognizedChar(char), +} +use TokenizeErrorCode::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct TokenizeError<'input> { + pub code: TokenizeErrorCode<'input>, + pub span: Range, +} + +#[derive(Debug, Clone, Default)] +pub struct TokenizeOptions { + identifiers_contain_numbers: bool, +} + +pub fn tokenize(input: &str) -> Result>, TokenizeError> { + tokenize_with_options(input, TokenizeOptions::default()) +} + +pub fn tokenize_with_options( + input: &str, + options: TokenizeOptions, +) -> Result>, TokenizeError> { + let mut tokens = Vec::with_capacity(16); + let mut chars = input.chars().enumerate().peekable(); + + macro_rules! push_token { + ($token:expr, $pos:expr, $len:expr) => { + tokens.push(Token { + value: $token, + span: Range { + start: $pos, + end: $pos + $len, + }, + }) + }; + } + + while let Some((cpos, c)) = chars.next() { + match c { + '+' => push_token!(Op(Add), cpos, 1), + '-' => push_token!(Op(Sub), cpos, 1), + '*' => push_token!(Op(Mul), cpos, 1), + '/' => push_token!(Op(Div), cpos, 1), + '%' => push_token!(Op(Mod), cpos, 1), + '^' => push_token!(Op(Pow), cpos, 1), + '=' => push_token!(Op(Eq), cpos, 1), + '!' => push_token!(Op(Exclaim), cpos, 1), + + '(' => push_token!(Symbol(LP), cpos, 1), + ')' => push_token!(Symbol(RP), cpos, 1), + ',' => push_token!(Symbol(Comma), cpos, 1), + '|' => push_token!(Symbol(Pipe), cpos, 1), + + _ => { + if c.is_ascii_digit() || c == '.' { + let start = cpos; + let mut end = start + 1; + while let Some((_, nc)) = chars.peek() { + if nc.is_ascii_digit() || *nc == '.' { + chars.next(); // Consume nc + end += 1; + } else { + break; + } + } + if let Ok(num) = input[start..end].parse::() { + push_token!(Num(num), start, end - start); + } else { + return Err(TokenizeError { + code: InvalidNumber(&input[start..end]), + span: start..end, + }); + } + } else if c == '_' || c.is_alphabetic() { + let start = cpos; + let mut end = start + 1; + while let Some((_, nc)) = chars.peek() { + // If it is any of _ A-z (or digits if option) + if *nc == '_' + || nc.is_alphanumeric() + || (options.identifiers_contain_numbers && nc.is_ascii_digit()) + { + chars.next(); // Consume next character + end += 1; + } else { + break; + } + } + push_token!(Id(&input[start..end]), start, end - start); + } else if !c.is_whitespace() { + return Err(TokenizeError { + code: UnrecognizedChar(c), + span: cpos..cpos + 1, + }); + } + } + } + } + Ok(tokens) +} diff --git a/tests/.env.dev b/tests/.env.dev index 0f654ec..33319e3 100644 --- a/tests/.env.dev +++ b/tests/.env.dev @@ -1 +1,2 @@ SECRET=6969 +ENV_VAR=69 diff --git a/tests/test_nconst.rs b/tests/test_nconst.rs index da0c972..54cd19f 100644 --- a/tests/test_nconst.rs +++ b/tests/test_nconst.rs @@ -86,7 +86,7 @@ fn test_nconst() { struct Wrapper(PhantomData); - type ActualType = nconst![-84938493]; + type ActualType = nconst![84938493]; let _wrapper = Wrapper::(PhantomData); @@ -159,3 +159,40 @@ fn test_nconst() { >::INT ); } + +#[test] +fn test_nconst_math_exprs_no_sign() { + use typenum::{assert_type_eq, N5}; + use typenum_consts::nconst; + type D = nconst![{ + a = 10; + b = 5; + b - a; // Last statement is always the final returned value to be casted into `typenum` type-level integer, U15 + }]; + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); + assert_type_eq!(D, N5); +} + +#[test] +fn test_env_give_negative_int() { + use typenum::{assert_type_eq, N69}; + use typenum_consts::nconst; + type D = nconst![env!("NENV_VAR");]; + assert_type_eq!(D, N69); + + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); +} diff --git a/tests/test_pconst.rs b/tests/test_pconst.rs index 0cee1d8..ee07592 100644 --- a/tests/test_pconst.rs +++ b/tests/test_pconst.rs @@ -86,7 +86,7 @@ fn test_pconst() { struct Wrapper(PhantomData); - type ActualType = pconst![+84938493]; + type ActualType = pconst![84938493]; let _wrapper = Wrapper::(PhantomData); @@ -159,3 +159,32 @@ fn test_pconst() { >::INT ); } + +#[test] +fn test_file_path_works() { + use typenum::{assert_type_eq, consts::P69}; + use typenum_consts::pconst; + + assert_type_eq!(pconst![env!("ENV_VAR", "tests/.env.dev");], P69); + assert_type_eq!(pconst![env!("ENV_VAR", "tests/.env.dev")], P69); +} + +#[test] +fn test_pconst_math_exprs_no_sign() { + use typenum::{assert_type_eq, P5}; + use typenum_consts::pconst; + type D = pconst![{ + a = 10; + b = 5; + a - b; // Last statement is always the final returned value to be casted into `typenum` type-level integer, U15 + }]; + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); + assert_type_eq!(D, P5); +} diff --git a/tests/test_exprs.rs b/tests/test_tnconst.rs similarity index 100% rename from tests/test_exprs.rs rename to tests/test_tnconst.rs diff --git a/tests/test_uconst.rs b/tests/test_uconst.rs index cf62873..195c8eb 100644 --- a/tests/test_uconst.rs +++ b/tests/test_uconst.rs @@ -130,62 +130,10 @@ fn test_tnconst_one() { typenum::assert_type_eq!(ExpectedType, tnconst![84938493]); } -// #[test] -// fn test_uconst_two() { -// let _wrapper = Wrapper::(PhantomData); - -// type ExpectedType = ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U7>, -// ::typenum::consts::U8, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U6>, -// ::typenum::consts::U4, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U5>, -// ::typenum::consts::U3, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U4>, -// ::typenum::consts::U3, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U3>, -// ::typenum::consts::U8, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U2>, -// ::typenum::consts::U4, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp<::typenum::consts::U10, ::typenum::consts::U1>, -// ::typenum::consts::U3, -// >, -// ::typenum::Sum< -// ::typenum::Prod< -// ::typenum::Exp< -// ::typenum::consts::U10, -// ::typenum::consts::U0, -// >, -// ::typenum::consts::U3, -// >, -// ::typenum::consts::U0, -// >, -// >, -// >, -// >, -// >, -// >, -// >, -// >; +#[test] +fn test_file_path_works() { + use typenum::{assert_type_eq, consts::U69}; + use typenum_consts::uconst; -// typenum::assert_type_eq!(ExpectedType, uconst![+84338433]); -// } + assert_type_eq!(uconst![env!("ENV_VAR", "tests/.env.dev");], U69); +} diff --git a/trybuild_tests/compile_fails/test_compile_fail.rs b/trybuild_tests/compile_fails/test_compile_fail.rs new file mode 100644 index 0000000..0e00b67 --- /dev/null +++ b/trybuild_tests/compile_fails/test_compile_fail.rs @@ -0,0 +1,238 @@ +#[cfg(target_pointer_width = "32")] +type I32OrI64 = i32; +#[cfg(target_pointer_width = "64")] +type I32OrI64 = i64; + +fn test_tnconst() { + use typenum_consts::tnconst; + + type A = tnconst![+ env!("BAD_SECRET");]; + type B = tnconst![+ env!("BAD_SECRET", "");]; + type C = tnconst![+ env!("BAD_SECRET",,);]; + type D = tnconst![+ env!(,,"BAD_SECRET");]; + type E = tnconst![+ env!("", );]; + type F = tnconst![+ env!("", "");]; + type G = tnconst![% env!("", "");]; + type H = tnconst![{ + x = 69; + lame; + }]; + type I = tnconst![-{ + a = 5; + b = 10; + a + b + }]; + type J = tnconst![+ { + a = 5; + b = 10; + a - b + }]; + type K = tnconst![{ + a = 5; + b = 10; + a - b + }]; + type L = tnconst![- env!("BAD_SECRET");]; + type M = tnconst![- env!("BAD_SECRET", "");]; + type N = tnconst![- env!("BAD_SECRET",,);]; + type O = tnconst![- env!(,,"BAD_SECRET");]; + type P = tnconst![- env!("", );]; + type Q = tnconst![- env!("", "");]; + type R = tnconst![- env!("ENV_VAR");]; + type S = tnconst![+ env!("NENV_VAR");]; + type T = tnconst![ env!("NENV_VAR");]; + println!("test_tnconst Passed!"); +} + +fn test_pconst() { + use typenum_consts::pconst; + + type A = pconst![ env!("BAD_SECRET");]; + type B = pconst![ env!("BAD_SECRET", "");]; + type C = pconst![ env!("BAD_SECRET",,);]; + type D = pconst![ env!(,,"BAD_SECRET");]; + type E = pconst![ env!("", );]; + type F = pconst![ env!("", "");]; + type G = pconst![% env!("", "");]; + type H = pconst![ 6969;]; + type J = pconst![ 6969 {};]; + type K = pconst![ 6969 {}]; + type I = pconst![{ + x = 69; + lame; + }]; + type M = pconst![+ 69]; + type N = pconst![+ env!("ENV_VAR");]; + type O = pconst![+ { + a = 5; + b = 10; + a + b + }]; + type P = pconst![{ + a = 5; + b = 10; + a - b + }]; + + println!("test_pconst Passed!"); +} + +fn test_nconst() { + use typenum_consts::nconst; + + type A = nconst![ env!("BAD_SECRET");]; + type B = nconst![ env!("BAD_SECRET", "");]; + type C = nconst![ env!("BAD_SECRET",,);]; + type D = nconst![ env!(,,"BAD_SECRET");]; + type E = nconst![ env!("", );]; + type F = nconst![ env!("", "");]; + type G = nconst![% env!("", "");]; + type H = nconst![ 6969;]; + type J = nconst![ 6969 {};]; + type K = nconst![ 6969 {}]; + type I = nconst![{ + x = 69; + lame; + }]; + type M = nconst![{}]; + type R = nconst![-69]; + type N = nconst![- env!("ENV_VAR");]; + type O = nconst![-{ + a = 5; + b = 10; + a + b + }]; + type P = nconst![{ + a = 5; + b = 10; + a + b + }]; + + println!("test_nconst Passed!"); +} + +fn test_uconst() { + use typenum_consts::uconst; + + type A = uconst![ env!("BAD_SECRET");]; + type B = uconst![ env!("BAD_SECRET", "");]; + type C = uconst![ env!("BAD_SECRET",,);]; + type D = uconst![ env!(,,"BAD_SECRET");]; + type E = uconst![ env!("", );]; + type F = uconst![ env!("", "");]; + type G = uconst![% env!("", "");]; + type H = uconst![ 6969;]; + type J = uconst![ 6969 {};]; + type K = uconst![ 6969 {}]; + type I = uconst![{ + x = 69; + lame; + }]; + type N = uconst![6969;]; + type M = uconst![6969;]; + type O = uconst![env!("ENV_VAR");;]; + type P = uconst![ { + 100 - 6969; + };]; + type R = uconst![-69]; + type S = uconst![- env!("ENV_VAR");]; + type T = uconst![-{ + a = 5; + b = 10; + a + b + }]; + type X = uconst![-{ + a = 5; + b = 10; + a - b + }]; + type U = uconst![+ 69]; + type V = uconst![+ env!("ENV_VAR");]; + type W = uconst![+ { + a = 5; + b = 10; + a + b + }]; + type Y = uconst![+ { + a = 5; + b = 10; + a - b + }]; + println!("test_uconst Passed!"); +} + +fn main() { + test_tnconst(); + test_pconst(); + test_nconst(); + test_uconst(); + test_nconst_math_exprs_positive_result_no_sign(); + test_env_give_positive_int_nconst(); +} + +fn test_nconst_math_exprs_positive_result_no_sign() { + use typenum::{assert_type_eq, N15}; + use typenum_consts::nconst; + type D = nconst![{ + a = 10; + b = 5; + b + a; // Last statement is always the final returned value to be casted into `typenum` type-level integer, U15 + }]; + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); + assert_type_eq!(D, N15); +} + +fn test_env_give_positive_int_nconst() { + use typenum::{assert_type_eq, N69}; + use typenum_consts::nconst; + type D = nconst![env!("ENV_VAR");]; + assert_type_eq!(D, N69); + + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); +} + +fn test_env_give_negative_int_pconst() { + use typenum::{assert_type_eq, P69}; + use typenum_consts::pconst; + type D = pconst![env!("NENV_VAR");]; + assert_type_eq!(D, P69); + + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); +} + +fn test_env_give_negative_int_uconst() { + use typenum::{assert_type_eq, U69}; + use typenum_consts::uconst; + type D = uconst![env!("NENV_VAR");]; + assert_type_eq!(D, U69); + + #[cfg(target_pointer_width = "32")] + type I32OrI64 = i32; + #[cfg(target_pointer_width = "64")] + type I32OrI64 = i64; + assert_eq!( + >::INT, + >::INT, + ); +} diff --git a/trybuild_tests/compile_fails/test_compile_fail.stderr b/trybuild_tests/compile_fails/test_compile_fail.stderr new file mode 100644 index 0000000..72f9c87 --- /dev/null +++ b/trybuild_tests/compile_fails/test_compile_fail.stderr @@ -0,0 +1,437 @@ +error: failed to read `BAD_SECRET` key from the environment, err = environment variable not found + --> trybuild_tests/compile_fails/test_compile_fail.rs:9:30 + | +9 | type A = tnconst![+ env!("BAD_SECRET");]; + | ^^^^^^^^^^^^ + +error: path cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:10:44 + | +10 | type B = tnconst![+ env!("BAD_SECRET", "");]; + | ^^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:11:43 + | +11 | type C = tnconst![+ env!("BAD_SECRET",,);]; + | ^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:12:30 + | +12 | type D = tnconst![+ env!(,,"BAD_SECRET");]; + | ^ + +error: unexpected end of input, expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:13:34 + | +13 | type E = tnconst![+ env!("", );]; + | ^ + +error: key cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:14:30 + | +14 | type F = tnconst![+ env!("", "");]; + | ^^ + +error: expected integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:15:23 + | +15 | type G = tnconst![% env!("", "");]; + | ^ + +error: VarDoesNotExist("lame") + --> trybuild_tests/compile_fails/test_compile_fail.rs:18:9 + | +18 | lame; + | ^^^^ + +error: expressions should be evaluated to a negative integer, got `15` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:23:9 + | +23 | a + b + | ^ + +error: expressions should be evaluated to a positive integer, got `-5` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:28:9 + | +28 | a - b + | ^ + +error: expressions should be evaluated to a non-negative or positive integer, got `-5` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:33:9 + | +33 | a - b + | ^ + +error: failed to read `BAD_SECRET` key from the environment, err = environment variable not found + --> trybuild_tests/compile_fails/test_compile_fail.rs:35:30 + | +35 | type L = tnconst![- env!("BAD_SECRET");]; + | ^^^^^^^^^^^^ + +error: path cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:36:44 + | +36 | type M = tnconst![- env!("BAD_SECRET", "");]; + | ^^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:37:43 + | +37 | type N = tnconst![- env!("BAD_SECRET",,);]; + | ^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:38:30 + | +38 | type O = tnconst![- env!(,,"BAD_SECRET");]; + | ^ + +error: unexpected end of input, expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:39:34 + | +39 | type P = tnconst![- env!("", );]; + | ^ + +error: key cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:40:30 + | +40 | type Q = tnconst![- env!("", "");]; + | ^^ + +error: invocation of `env` macro does not return a negative integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:41:25 + | +41 | type R = tnconst![- env!("ENV_VAR");]; + | ^^^ + +error: invocation of `env` macro does not return a positive integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:42:25 + | +42 | type S = tnconst![+ env!("NENV_VAR");]; + | ^^^ + +error: invocation of `env` macro does not return a positive integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:43:24 + | +43 | type T = tnconst![ env!("NENV_VAR");]; + | ^^^ + +error: failed to read `BAD_SECRET` key from the environment, err = environment variable not found + --> trybuild_tests/compile_fails/test_compile_fail.rs:50:28 + | +50 | type A = pconst![ env!("BAD_SECRET");]; + | ^^^^^^^^^^^^ + +error: path cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:51:42 + | +51 | type B = pconst![ env!("BAD_SECRET", "");]; + | ^^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:52:41 + | +52 | type C = pconst![ env!("BAD_SECRET",,);]; + | ^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:53:28 + | +53 | type D = pconst![ env!(,,"BAD_SECRET");]; + | ^ + +error: unexpected end of input, expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:54:32 + | +54 | type E = pconst![ env!("", );]; + | ^ + +error: key cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:55:28 + | +55 | type F = pconst![ env!("", "");]; + | ^^ + +error: expected integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:56:22 + | +56 | type G = pconst![% env!("", "");]; + | ^ + +error: expected `;` + --> trybuild_tests/compile_fails/test_compile_fail.rs:58:28 + | +58 | type J = pconst![ 6969 {};]; + | ^ + +error: expected `;` + --> trybuild_tests/compile_fails/test_compile_fail.rs:59:28 + | +59 | type K = pconst![ 6969 {}]; + | ^ + +error: VarDoesNotExist("lame") + --> trybuild_tests/compile_fails/test_compile_fail.rs:62:9 + | +62 | lame; + | ^^^^ + +error: when using `pconst`, the first character passed cannot be a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:64:22 + | +64 | type M = pconst![+ 69]; + | ^ + +error: when using `pconst`, the first character passed cannot be a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:65:22 + | +65 | type N = pconst![+ env!("ENV_VAR");]; + | ^ + +error: when using `pconst`, the first character passed cannot be a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:66:22 + | +66 | type O = pconst![+ { + | ^ + +error: expressions should be evaluated to a positive integer, got `-5` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:74:9 + | +74 | a - b + | ^ + +error: failed to read `BAD_SECRET` key from the environment, err = environment variable not found + --> trybuild_tests/compile_fails/test_compile_fail.rs:83:28 + | +83 | type A = nconst![ env!("BAD_SECRET");]; + | ^^^^^^^^^^^^ + +error: path cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:84:42 + | +84 | type B = nconst![ env!("BAD_SECRET", "");]; + | ^^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:85:41 + | +85 | type C = nconst![ env!("BAD_SECRET",,);]; + | ^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:86:28 + | +86 | type D = nconst![ env!(,,"BAD_SECRET");]; + | ^ + +error: unexpected end of input, expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:87:32 + | +87 | type E = nconst![ env!("", );]; + | ^ + +error: key cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:88:28 + | +88 | type F = nconst![ env!("", "");]; + | ^^ + +error: expected integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:89:22 + | +89 | type G = nconst![% env!("", "");]; + | ^ + +error: expected `;` + --> trybuild_tests/compile_fails/test_compile_fail.rs:91:28 + | +91 | type J = nconst![ 6969 {};]; + | ^ + +error: expected `;` + --> trybuild_tests/compile_fails/test_compile_fail.rs:92:28 + | +92 | type K = nconst![ 6969 {}]; + | ^ + +error: VarDoesNotExist("lame") + --> trybuild_tests/compile_fails/test_compile_fail.rs:95:9 + | +95 | lame; + | ^^^^ + +error: unexpected end of input, the content within the block delimited by `{...}` must not be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:97:23 + | +97 | type M = nconst![{}]; + | ^ + +error: when using `nconst`, the first character passed cannot be a `-` + --> trybuild_tests/compile_fails/test_compile_fail.rs:98:22 + | +98 | type R = nconst![-69]; + | ^ + +error: when using `nconst`, the first character passed cannot be a `-` + --> trybuild_tests/compile_fails/test_compile_fail.rs:99:22 + | +99 | type N = nconst![- env!("ENV_VAR");]; + | ^ + +error: when using `nconst`, the first character passed cannot be a `-` + --> trybuild_tests/compile_fails/test_compile_fail.rs:100:22 + | +100 | type O = nconst![-{ + | ^ + +error: expressions should be evaluated to a negative integer, got `15` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:108:9 + | +108 | a + b + | ^ + +error: failed to read `BAD_SECRET` key from the environment, err = environment variable not found + --> trybuild_tests/compile_fails/test_compile_fail.rs:117:28 + | +117 | type A = uconst![ env!("BAD_SECRET");]; + | ^^^^^^^^^^^^ + +error: path cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:118:42 + | +118 | type B = uconst![ env!("BAD_SECRET", "");]; + | ^^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:119:41 + | +119 | type C = uconst![ env!("BAD_SECRET",,);]; + | ^ + +error: expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:120:28 + | +120 | type D = uconst![ env!(,,"BAD_SECRET");]; + | ^ + +error: unexpected end of input, expected string literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:121:32 + | +121 | type E = uconst![ env!("", );]; + | ^ + +error: key cannot be empty + --> trybuild_tests/compile_fails/test_compile_fail.rs:122:28 + | +122 | type F = uconst![ env!("", "");]; + | ^^ + +error: expected integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:123:22 + | +123 | type G = uconst![% env!("", "");]; + | ^ + +error: expected `;` + --> trybuild_tests/compile_fails/test_compile_fail.rs:125:28 + | +125 | type J = uconst![ 6969 {};]; + | ^ + +error: expected `;` + --> trybuild_tests/compile_fails/test_compile_fail.rs:126:28 + | +126 | type K = uconst![ 6969 {}]; + | ^ + +error: VarDoesNotExist("lame") + --> trybuild_tests/compile_fails/test_compile_fail.rs:129:9 + | +129 | lame; + | ^^^^ + +error: unexpected token + --> trybuild_tests/compile_fails/test_compile_fail.rs:133:38 + | +133 | type O = uconst![env!("ENV_VAR");;]; + | ^ + +error: expressions should be evaluated to a non-negative or positive integer, got `-6869` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:135:9 + | +135 | 100 - 6969; + | ^^^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:137:22 + | +137 | type R = uconst![-69]; + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:138:22 + | +138 | type S = uconst![- env!("ENV_VAR");]; + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:139:22 + | +139 | type T = uconst![-{ + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:144:22 + | +144 | type X = uconst![-{ + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:149:22 + | +149 | type U = uconst![+ 69]; + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:150:22 + | +150 | type V = uconst![+ env!("ENV_VAR");]; + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:151:22 + | +151 | type W = uconst![+ { + | ^ + +error: when using `uconst`, the first character passed cannot be a `-` or a `+` + --> trybuild_tests/compile_fails/test_compile_fail.rs:156:22 + | +156 | type Y = uconst![+ { + | ^ + +error: expressions should be evaluated to a negative integer, got `15` instead + --> trybuild_tests/compile_fails/test_compile_fail.rs:179:9 + | +179 | b + a; // Last statement is always the final returned value to be casted into `typenum` type-level integer, U15 + | ^ + +error: invocation of `env` macro does not return a negative integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:195:22 + | +195 | type D = nconst![env!("ENV_VAR");]; + | ^^^ + +error: invocation of `env` macro does not return a positive integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:211:22 + | +211 | type D = pconst![env!("NENV_VAR");]; + | ^^^ + +error: invocation of `env` macro does not return a positive integer literal + --> trybuild_tests/compile_fails/test_compile_fail.rs:227:22 + | +227 | type D = uconst![env!("NENV_VAR");]; + | ^^^ diff --git a/trybuild_tests/compile_fails/test_dotenv_fail.rs b/trybuild_tests/compile_fails/test_dotenv_fail.rs deleted file mode 100644 index 0b3aca2..0000000 --- a/trybuild_tests/compile_fails/test_dotenv_fail.rs +++ /dev/null @@ -1,92 +0,0 @@ -fn test_tnconst() { - use typenum_consts::tnconst; - - type A = tnconst![+ env!("SEXY_SECRET");]; - type B = tnconst![+ env!("SEXY_SECRET", "");]; - type C = tnconst![+ env!("SEXY_SECRET",,);]; - type D = tnconst![+ env!(,,"SEXY_SECRET");]; - type E = tnconst![+ env!("", );]; - type F = tnconst![+ env!("", "");]; - type G = tnconst![% env!("", "");]; - type H = tnconst![{ - x = 69; - lame; - }]; - println!("test_tnconst Passed!"); -} - -fn test_pconst() { - use typenum_consts::pconst; - - type A = pconst![+ env!("SEXY_SECRET");]; - type B = pconst![+ env!("SEXY_SECRET", "");]; - type C = pconst![+ env!("SEXY_SECRET",,);]; - type D = pconst![+ env!(,,"SEXY_SECRET");]; - type E = pconst![+ env!("", );]; - type F = pconst![+ env!("", "");]; - type G = pconst![% env!("", "");]; - type H = pconst![+ 6969;]; - type J = pconst![+ 6969 {};]; - type K = pconst![+ 6969 {}]; - type L = pconst![+ 0 ]; - assert_eq!(::USIZE, 0); - type I = pconst![{ - x = 69; - lame; - }]; - println!("test_pconst Passed!"); -} - -fn test_nconst() { - use typenum_consts::nconst; - - type A = nconst![- env!("SEXY_SECRET");]; - type B = nconst![- env!("SEXY_SECRET", "");]; - type C = nconst![- env!("SEXY_SECRET",,);]; - type D = nconst![- env!(,,"SEXY_SECRET");]; - type E = nconst![- env!("", );]; - type F = nconst![- env!("", "");]; - type G = nconst![% env!("", "");]; - type H = nconst![- 6969;]; - type J = nconst![- 6969 {};]; - type K = nconst![- 6969 {}]; - type L = nconst![-0]; - assert_eq!(::USIZE, 0); - type I = nconst![{ - x = 69; - lame; - }]; - type M = nconst![-{}]; - println!("test_nconst Passed!"); -} - -fn test_uconst() { - use typenum_consts::uconst; - - type A = uconst![ env!("SEXY_SECRET");]; - type B = uconst![ env!("SEXY_SECRET", "");]; - type C = uconst![ env!("SEXY_SECRET",,);]; - type D = uconst![ env!(,,"SEXY_SECRET");]; - type E = uconst![ env!("", );]; - type F = uconst![ env!("", "");]; - type G = uconst![% env!("", "");]; - type H = uconst![ 6969;]; - type J = uconst![ 6969 {};]; - type K = uconst![ 6969 {}]; - type L = uconst![0]; - assert_eq!(::USIZE, 0); - type I = uconst![{ - x = 69; - lame; - }]; - type N = uconst![+ 6969;]; - type M = uconst![- 6969;]; - println!("test_uconst Passed!"); -} - -fn main() { - test_tnconst(); - test_pconst(); - test_nconst(); - test_uconst(); -} diff --git a/trybuild_tests/compile_passes/.env.dev b/trybuild_tests/compile_passes/.env.dev deleted file mode 100644 index 0f654ec..0000000 --- a/trybuild_tests/compile_passes/.env.dev +++ /dev/null @@ -1 +0,0 @@ -SECRET=6969 diff --git a/trybuild_tests/compile_passes/test_dotenv_passes.rs b/trybuild_tests/compile_passes/test_compile_passes.rs similarity index 67% rename from trybuild_tests/compile_passes/test_dotenv_passes.rs rename to trybuild_tests/compile_passes/test_compile_passes.rs index 3205bc5..5254bcc 100644 --- a/trybuild_tests/compile_passes/test_dotenv_passes.rs +++ b/trybuild_tests/compile_passes/test_compile_passes.rs @@ -1,4 +1,4 @@ -/// export ENV_VAR="69" && cargo test +/// export ENV_VAR="69" && export NENV_VAR="-69" && cargo test fn test_tnconst() { // use std::{env, fs}; @@ -6,7 +6,7 @@ fn test_tnconst() { use typenum_consts::tnconst; type A = tnconst![+ env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::PInt - type B = tnconst![- env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::NInt + type B = tnconst![- env!("NENV_VAR");]; // Read from environment, get a literal int => typenum::NInt type C = tnconst![ env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::UInt assert_type_eq!(A, P69); @@ -30,8 +30,7 @@ fn test_tnconst() { type I = tnconst![-{ a = 10; b = 5; - a + b; - c = 69 // Last statement is always the final returned value to be casted into `typenum` type-level integer, N69 + b - a; }]; type J = tnconst![{ a = 10; @@ -41,7 +40,7 @@ fn test_tnconst() { }]; assert_type_eq!(H, P69); - assert_type_eq!(I, N69); + assert_type_eq!(I, N5); assert_type_eq!(J, U69); println!("`test_tnconst` Passed!"); @@ -52,24 +51,14 @@ fn test_pconst() { use typenum::{assert_type_eq, consts::*}; use typenum_consts::pconst; - type A = pconst![+ env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::PInt type C = pconst![ env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::UInt - assert_type_eq!(A, P69); assert_type_eq!(C, P69); - type D = pconst![+ 123]; type F = pconst![123]; - assert_type_eq!(D, P123); assert_type_eq!(F, P123); - type H = pconst![+ { - a = 10; - b = 5; - a + b; - c = 69 // Last statement is always the final returned value to be casted into `typenum` type-level integer, P69 - }]; type J = pconst![{ a = 10; b = 5; @@ -77,7 +66,6 @@ fn test_pconst() { c = 69 // Last statement is always the final returned value to be casted into `typenum` type-level integer, U69 }]; - assert_type_eq!(H, P69); assert_type_eq!(J, P69); println!("`test_pconst` Passed!"); @@ -88,32 +76,21 @@ fn test_nconst() { use typenum::{assert_type_eq, consts::*}; use typenum_consts::nconst; - type B = nconst![- env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::NInt - type C = nconst![ env!("ENV_VAR");]; // Read from environment, get a literal int => typenum::UInt + type C = nconst![ env!("NENV_VAR");]; // Read from environment, get a literal int => typenum::UInt - assert_type_eq!(B, N69); assert_type_eq!(C, N69); - type E = nconst![-123]; type F = nconst![123]; - assert_type_eq!(E, N123); assert_type_eq!(F, N123); - type I = nconst![-{ - a = 10; - b = 5; - a + b; - c = 69 // Last statement is always the final returned value to be casted into `typenum` type-level integer, N69 - }]; type J = nconst![{ a = 10; b = 5; a + b; - c = 69 // Last statement is always the final returned value to be casted into `typenum` type-level integer, U69 + c = -69 // Last statement is always the final returned value to be casted into `typenum` type-level integer, U69 }]; - assert_type_eq!(I, N69); assert_type_eq!(J, N69); println!("`test_nconst` Passed!"); diff --git a/vendors/rsc b/vendors/rsc deleted file mode 160000 index b6b2bc2..0000000 --- a/vendors/rsc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6b2bc24bf9fbc4b3bee8fa14c1fec86bf82e4d2