summaryrefslogtreecommitdiffstats
path: root/third_party/rust/litrs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:13:27 +0000
commit40a355a42d4a9444dc753c04c6608dade2f06a23 (patch)
tree871fc667d2de662f171103ce5ec067014ef85e61 /third_party/rust/litrs
parentAdding upstream version 124.0.1. (diff)
downloadfirefox-40a355a42d4a9444dc753c04c6608dade2f06a23.tar.xz
firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.zip
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/litrs')
-rw-r--r--third_party/rust/litrs/.cargo-checksum.json1
-rw-r--r--third_party/rust/litrs/CHANGELOG.md103
-rw-r--r--third_party/rust/litrs/Cargo.toml51
-rw-r--r--third_party/rust/litrs/LICENSE-APACHE176
-rw-r--r--third_party/rust/litrs/LICENSE-MIT25
-rw-r--r--third_party/rust/litrs/README.md88
-rw-r--r--third_party/rust/litrs/src/bool/mod.rs55
-rw-r--r--third_party/rust/litrs/src/bool/tests.rs48
-rw-r--r--third_party/rust/litrs/src/byte/mod.rs107
-rw-r--r--third_party/rust/litrs/src/byte/tests.rs188
-rw-r--r--third_party/rust/litrs/src/bytestr/mod.rs126
-rw-r--r--third_party/rust/litrs/src/bytestr/tests.rs224
-rw-r--r--third_party/rust/litrs/src/char/mod.rs105
-rw-r--r--third_party/rust/litrs/src/char/tests.rs227
-rw-r--r--third_party/rust/litrs/src/err.rs371
-rw-r--r--third_party/rust/litrs/src/escape.rs262
-rw-r--r--third_party/rust/litrs/src/float/mod.rs257
-rw-r--r--third_party/rust/litrs/src/float/tests.rs253
-rw-r--r--third_party/rust/litrs/src/impls.rs401
-rw-r--r--third_party/rust/litrs/src/integer/mod.rs356
-rw-r--r--third_party/rust/litrs/src/integer/tests.rs357
-rw-r--r--third_party/rust/litrs/src/lib.rs370
-rw-r--r--third_party/rust/litrs/src/parse.rs125
-rw-r--r--third_party/rust/litrs/src/string/mod.rs125
-rw-r--r--third_party/rust/litrs/src/string/tests.rs278
-rw-r--r--third_party/rust/litrs/src/test_util.rs128
-rw-r--r--third_party/rust/litrs/src/tests.rs349
27 files changed, 5156 insertions, 0 deletions
diff --git a/third_party/rust/litrs/.cargo-checksum.json b/third_party/rust/litrs/.cargo-checksum.json
new file mode 100644
index 0000000000..d12ccffda0
--- /dev/null
+++ b/third_party/rust/litrs/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"03cea7c394dd09087f6b2c7ba4b4641b5c2c50b32b7286cabd5be4850f62f170","Cargo.toml":"6ef884164a0139f0591a381ada2c99d850d38e5f3af3451efa12f808f8a799e0","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"7dc1552e88f49132cb358b1b962fc5e79fa42d70bcbb88c526d33e45b8e98036","README.md":"533d31adf3b4258b838cd6a1cdb58139e2cf761c3c38aa4654f66f34335c9073","src/bool/mod.rs":"53c6eedfd94552689e51233fffb8a99ce9321a32db0f08de8b18d48cda9b1877","src/bool/tests.rs":"a0e6d034036aa04aac6b847bb561bdba759d85c78d4cbb7fb93f4422efb83656","src/byte/mod.rs":"ff2a3e6108a9b32ae0d925ec34735d20194d5c6b27af060516a46d21397c75be","src/byte/tests.rs":"ac36dace42cd151ac9d26cc35701bc8b65f8f1ed6ee1cfef4eeb6caa9dd702bc","src/bytestr/mod.rs":"8fd951374f7edc2077465cd4f97001eece46358f2bb0c45fddb2942aac6ee13b","src/bytestr/tests.rs":"194b28f157196260b1c2a612dfb36fb1dace491db2ed2bbb39227771ed6baf60","src/char/mod.rs":"2bb6f25da83670f18ec40f8a38565aa2294a4cdf81c8bbaf081531a32b6c6d0c","src/char/tests.rs":"9de497c8c7d7a139ff81f3d7bf8b5c682316d983bebb58c58d2af97f4cd26c35","src/err.rs":"54d000c4f37258c6886dd5b7069e2f5282e51aec3731feb77935582ae8c18908","src/escape.rs":"a944e95344df54c16bf4cc6a8fb01a81e2eac2aacd4758b938d3339212fce60c","src/float/mod.rs":"defaf83526acdc8f9b34c7d1ac17d866a93409dc392eb608160778d6bb4a1e25","src/float/tests.rs":"5875403f1a72104973ed83d0cf29d766e7b2fa5c23615c85a5f2eeed02b115c9","src/impls.rs":"c5dd37dd3ecd29c40a0ed243b907765a27729a1b1f73fa2c6762105feb6527bc","src/integer/mod.rs":"2b9109ddd34faf76fc9ce9dfb04bcc6aed4834231c74bd8a774bd256cc57c18a","src/integer/tests.rs":"01147ce9b6742bb1614cf863090699c54bf660b9f2c6a5eb529d67ae92230c0d","src/lib.rs":"2e79c8035d0fb77db9414b5569eeef13b6db8cde48ef2a45ffcf5f2492d02a4a","src/parse.rs":"e1fa4a76331d52f711e1b06cdba853a4f815281366f4f4f68b4c0a109f8a1734","src/string/mod.rs":"52a9cda38f7cd5b025bc5ec7edb8106487ba3d141789f5bc239c4561490cdc29","src/string/tests.rs":"1e0150ddd921a74ed5ebf6216708132d7768f3beb11a8c7bbfcf4ba01db40a5b","src/test_util.rs":"3badda83d7f256bb25b840820bc0d3a6523b4ded913555cbea5533b6ccad5654","src/tests.rs":"9f0dc2fe7a0eefb6575acd824767bb7d837a584dc7999ef59a457255a2cd7f3d"},"package":"b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"} \ No newline at end of file
diff --git a/third_party/rust/litrs/CHANGELOG.md b/third_party/rust/litrs/CHANGELOG.md
new file mode 100644
index 0000000000..e2927c2964
--- /dev/null
+++ b/third_party/rust/litrs/CHANGELOG.md
@@ -0,0 +1,103 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+
+## [Unreleased]
+
+## [0.4.1] - 2023-10-18
+- Fixed incorrectly labeling `27f32` a float literals in docs.
+- Added hint to integer literal docs about parsing as `u128`.
+
+## [0.4.0] - 2023-03-05
+### Added
+- Add ability to parse literals with arbitrary suffixes (e.g. `"foo"bla` or `23px`)
+- Add `suffix()` method to all literal types except `BoolLit`
+- Add `IntegerBase::value`
+- Add `from_suffix` and `suffix` methods to `FloatType` and `IntegerType`
+- Add `FromStr` and `Display` impls to `FloatType` and `IntegerType`
+
+### Changed
+- **Breaking**: Mark `FloatType` and `IntegerType` as `#[non_exhaustive]`
+- **Breaking**: Fix integer parsing for cases like `27f32`. `Literal::parse`
+ and `IntegerLit::parse` will both identify this as an integer literal.
+- **Breaking**: Fix float parsing by correctly rejecting inputs like `27f32`. A
+ float literal must have a period OR an exponent part, according to the spec.
+ Previously decimal integers were accepted in `FloatLit::parse`.
+- Improved some parts of the docs
+
+### Removed
+- **Breaking**: Remove `OwnedLiteral` and `SharedLiteral`
+
+## [0.3.0] - 2022-12-19
+### Breaking
+- Bump MSRV (minimal supported Rust version) to 1.54
+
+### Added
+- Add `raw_input` and `into_raw_input` to non-bool `*Lit` types
+- Add `impl From<*Lit> for pm::Literal` (for non-bool literals)
+- Add `impl From<BoolLit> for pm::Ident`
+
+### Fixed
+- Fix link to reference and clarify bool literals ([#7](https://github.com/LukasKalbertodt/litrs/pull/7))
+
+### Internals
+- Move lots of parsing code into non-generic functions (this hopefully reduces compile times)
+- To implement `[into_]raw_input` for integer and float literals, their
+ internals were changed a bit so that they store the full input string now.
+
+## [0.2.3] - 2021-06-09
+### Changed
+- Minor internal code change to bring MSRV from 1.52 to 1.42
+
+## [0.2.2] - 2021-06-09
+### Changed
+- Fixed (byte) string literal parsing by:
+ - Correctly handling "string continue" sequences
+ - Correctly converting `\n\r` into `\n`
+
+## [0.2.1] - 2021-06-04
+### Changed
+- Fixed the `expected` value of the error returned from `TryFrom<TokenTree>` impls in some cases
+
+## [0.2.0] - 2021-05-28
+### Changed
+- **Breaking**: rename `Error` to `ParseError`. That describes its purpose more
+ closely and is particular useful now that other error types exist in the library.
+
+### Removed
+- **Breaking**: remove `proc-macro` feature and instead offer the corresponding
+ `impl`s unconditionally. Since the feature didn't enable/disable a
+ dependency (`proc-macro` is a compiler provided crate) and since apparently
+ it works fine in `no_std` environments, I dropped this feature. I don't
+ currently see a reason why the corresponding impls should be conditional.
+
+### Added
+- `TryFrom<TokenTree> for litrs::Literal` impls
+- `From<*Lit> for litrs::Literal` impls
+- `TryFrom<proc_macro[2]::Literal> for *Lit`
+- `TryFrom<TokenTree> for *Lit`
+- `InvalidToken` error type for all new `TryFrom` impls
+
+
+## [0.1.1] - 2021-05-25
+### Added
+- `From` impls to create a `Literal` from references to proc-macro literal types:
+ - `From<&proc_macro::Literal>`
+ - `From<&proc_macro2::Literal>`
+- Better examples in README and repository
+
+## 0.1.0 - 2021-05-24
+### Added
+- Everything
+
+
+[Unreleased]: https://github.com/LukasKalbertodt/litrs/compare/v0.4.1...HEAD
+[0.4.1]: https://github.com/LukasKalbertodt/litrs/compare/v0.4.0...v0.4.1
+[0.4.0]: https://github.com/LukasKalbertodt/litrs/compare/v0.3.0...v0.4.0
+[0.3.0]: https://github.com/LukasKalbertodt/litrs/compare/v0.2.3...v0.3.0
+[0.2.3]: https://github.com/LukasKalbertodt/litrs/compare/v0.2.2...v0.2.3
+[0.2.2]: https://github.com/LukasKalbertodt/litrs/compare/v0.2.1...v0.2.2
+[0.2.1]: https://github.com/LukasKalbertodt/litrs/compare/v0.2.0...v0.2.1
+[0.2.0]: https://github.com/LukasKalbertodt/litrs/compare/v0.1.1...v0.2.0
+[0.1.1]: https://github.com/LukasKalbertodt/litrs/compare/v0.1.0...v0.1.1
diff --git a/third_party/rust/litrs/Cargo.toml b/third_party/rust/litrs/Cargo.toml
new file mode 100644
index 0000000000..6e65403490
--- /dev/null
+++ b/third_party/rust/litrs/Cargo.toml
@@ -0,0 +1,51 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+rust-version = "1.54"
+name = "litrs"
+version = "0.4.1"
+authors = ["Lukas Kalbertodt <lukas.kalbertodt@gmail.com>"]
+exclude = [".github"]
+description = """
+Parse and inspect Rust literals (i.e. tokens in the Rust programming language
+representing fixed values). Particularly useful for proc macros, but can also
+be used outside of a proc-macro context.
+"""
+documentation = "https://docs.rs/litrs/"
+readme = "README.md"
+keywords = [
+ "literal",
+ "parsing",
+ "proc-macro",
+ "type",
+ "procedural",
+]
+categories = [
+ "development-tools::procedural-macro-helpers",
+ "parser-implementations",
+ "development-tools::build-utils",
+]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/LukasKalbertodt/litrs/"
+
+[dependencies.proc-macro2]
+version = "1"
+optional = true
+
+[dependencies.unicode-xid]
+version = "0.2.4"
+optional = true
+
+[features]
+check_suffix = ["unicode-xid"]
+default = ["proc-macro2"]
diff --git a/third_party/rust/litrs/LICENSE-APACHE b/third_party/rust/litrs/LICENSE-APACHE
new file mode 100644
index 0000000000..1b5ec8b78e
--- /dev/null
+++ b/third_party/rust/litrs/LICENSE-APACHE
@@ -0,0 +1,176 @@
+ 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
diff --git a/third_party/rust/litrs/LICENSE-MIT b/third_party/rust/litrs/LICENSE-MIT
new file mode 100644
index 0000000000..4fa8658f71
--- /dev/null
+++ b/third_party/rust/litrs/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2020 Project Developers
+
+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/third_party/rust/litrs/README.md b/third_party/rust/litrs/README.md
new file mode 100644
index 0000000000..2307a5f429
--- /dev/null
+++ b/third_party/rust/litrs/README.md
@@ -0,0 +1,88 @@
+# `litrs`: parsing and inspecting Rust literals
+
+[<img alt="CI status of main" src="https://img.shields.io/github/actions/workflow/status/LukasKalbertodt/litrs/ci.yml?branch=main&label=CI&logo=github&logoColor=white&style=for-the-badge" height="23">](https://github.com/LukasKalbertodt/litrs/actions/workflows/ci.yml)
+[<img alt="Crates.io Version" src="https://img.shields.io/crates/v/litrs?logo=rust&style=for-the-badge" height="23">](https://crates.io/crates/litrs)
+[<img alt="docs.rs" src="https://img.shields.io/crates/v/litrs?color=blue&label=docs&style=for-the-badge" height="23">](https://docs.rs/litrs)
+
+`litrs` offers functionality to parse Rust literals, i.e. tokens in the Rust programming language that represent fixed values.
+For example: `27`, `"crab"`, `bool`.
+This is particularly useful for proc macros, but can also be used outside of a proc-macro context.
+
+**Why this library?**
+Unfortunately, the `proc_macro` API shipped with the compiler offers no easy way to inspect literals.
+There are mainly two libraries for this purpose:
+[`syn`](https://github.com/dtolnay/syn) and [`literalext`](https://github.com/mystor/literalext).
+The latter is deprecated.
+And `syn` is oftentimes overkill for the task at hand, especially when developing function-like proc-macros (e.g. `foo!(..)`).
+This crate is a lightweight alternative.
+Also, when it comes to literals, `litrs` offers a bit more flexibility and a few more features compared to `syn`.
+
+I'm interested in community feedback!
+If you consider using this, please speak your mind [in this issue](https://github.com/LukasKalbertodt/litrs/issues/1).
+
+## Example
+
+### In proc macro
+
+```rust
+use std::convert::TryFrom;
+use proc_macro::TokenStream;
+use litrs::Literal;
+
+#[proc_macro]
+pub fn foo(input: TokenStream) -> TokenStream {
+ // Please do proper error handling in your real code!
+ let first_token = input.into_iter().next().expect("no input");
+
+ // `try_from` will return an error if the token is not a literal.
+ match Literal::try_from(first_token) {
+ // Convenient methods to produce decent errors via `compile_error!`.
+ Err(e) => return e.to_compile_error(),
+
+ // You can now inspect your literal!
+ Ok(Literal::Integer(i)) => {
+ println!("Got an integer specified in base {:?}", i.base());
+
+ let value = i.value::<u64>().expect("integer literal too large");
+ println!("Is your integer even? {}", value % 2 == 0);
+ }
+ Ok(other) => {
+ println!("Got a non-integer literal");
+ }
+ }
+
+ TokenStream::new() // dummy output
+}
+```
+
+If you are expecting a specific kind of literal, you can also use this, which will return an error if the token is not a float literal.
+
+```rust
+FloatLit::try_from(first_token)
+```
+
+### Parsing from a `&str`
+
+Outside of a proc macro context you might want to parse a string directly.
+
+```rust
+use litrs::{FloatLit, Literal};
+
+let lit = Literal::parse("'🦀'").expect("failed to parse literal");
+let float_lit = FloatLit::parse("2.7e3").expect("failed to parse as float literal");
+```
+
+See [**the documentation**](https://docs.rs/litrs) or the `examples/` directory for more examples and information.
+
+
+<br />
+
+---
+
+## License
+
+Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
+2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in this project by you, as defined in the Apache-2.0 license,
+shall be dual licensed as above, without any additional terms or conditions.
diff --git a/third_party/rust/litrs/src/bool/mod.rs b/third_party/rust/litrs/src/bool/mod.rs
new file mode 100644
index 0000000000..d7b54a1b9f
--- /dev/null
+++ b/third_party/rust/litrs/src/bool/mod.rs
@@ -0,0 +1,55 @@
+use std::fmt;
+
+use crate::{ParseError, err::{perr, ParseErrorKind::*}};
+
+
+/// A bool literal: `true` or `false`. Also see [the reference][ref].
+///
+/// Notice that, strictly speaking, from Rust point of view "boolean literals" are not
+/// actual literals but [keywords].
+///
+/// [ref]: https://doc.rust-lang.org/reference/expressions/literal-expr.html#boolean-literal-expressions
+/// [keywords]: https://doc.rust-lang.org/reference/keywords.html#strict-keywords
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum BoolLit {
+ False,
+ True,
+}
+
+impl BoolLit {
+ /// Parses the input as a bool literal. Returns an error if the input is
+ /// invalid or represents a different kind of literal.
+ pub fn parse(s: &str) -> Result<Self, ParseError> {
+ match s {
+ "false" => Ok(Self::False),
+ "true" => Ok(Self::True),
+ _ => Err(perr(None, InvalidLiteral)),
+ }
+ }
+
+ /// Returns the actual Boolean value of this literal.
+ pub fn value(self) -> bool {
+ match self {
+ Self::False => false,
+ Self::True => true,
+ }
+ }
+
+ /// Returns the literal as string.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::False => "false",
+ Self::True => "true",
+ }
+ }
+}
+
+impl fmt::Display for BoolLit {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/bool/tests.rs b/third_party/rust/litrs/src/bool/tests.rs
new file mode 100644
index 0000000000..4b829244b8
--- /dev/null
+++ b/third_party/rust/litrs/src/bool/tests.rs
@@ -0,0 +1,48 @@
+use crate::{
+ Literal, BoolLit,
+ test_util::assert_parse_ok_eq,
+};
+
+macro_rules! assert_bool_parse {
+ ($input:literal, $expected:expr) => {
+ assert_parse_ok_eq(
+ $input, Literal::parse($input), Literal::Bool($expected), "Literal::parse");
+ assert_parse_ok_eq($input, BoolLit::parse($input), $expected, "BoolLit::parse");
+ };
+}
+
+
+
+#[test]
+fn parse_ok() {
+ assert_bool_parse!("false", BoolLit::False);
+ assert_bool_parse!("true", BoolLit::True);
+}
+
+#[test]
+fn parse_err() {
+ assert!(Literal::parse("fa").is_err());
+ assert!(Literal::parse("fal").is_err());
+ assert!(Literal::parse("fals").is_err());
+ assert!(Literal::parse(" false").is_err());
+ assert!(Literal::parse("false ").is_err());
+ assert!(Literal::parse("False").is_err());
+
+ assert!(Literal::parse("tr").is_err());
+ assert!(Literal::parse("tru").is_err());
+ assert!(Literal::parse(" true").is_err());
+ assert!(Literal::parse("true ").is_err());
+ assert!(Literal::parse("True").is_err());
+}
+
+#[test]
+fn value() {
+ assert!(!BoolLit::False.value());
+ assert!(BoolLit::True.value());
+}
+
+#[test]
+fn as_str() {
+ assert_eq!(BoolLit::False.as_str(), "false");
+ assert_eq!(BoolLit::True.as_str(), "true");
+}
diff --git a/third_party/rust/litrs/src/byte/mod.rs b/third_party/rust/litrs/src/byte/mod.rs
new file mode 100644
index 0000000000..ffdff5d04a
--- /dev/null
+++ b/third_party/rust/litrs/src/byte/mod.rs
@@ -0,0 +1,107 @@
+use core::fmt;
+
+use crate::{
+ Buffer, ParseError,
+ err::{perr, ParseErrorKind::*},
+ escape::unescape,
+ parse::check_suffix,
+};
+
+
+/// A (single) byte literal, e.g. `b'k'` or `b'!'`.
+///
+/// See [the reference][ref] for more information.
+///
+/// [ref]: https://doc.rust-lang.org/reference/tokens.html#byte-literals
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ByteLit<B: Buffer> {
+ raw: B,
+ /// Start index of the suffix or `raw.len()` if there is no suffix.
+ start_suffix: usize,
+ value: u8,
+}
+
+impl<B: Buffer> ByteLit<B> {
+ /// Parses the input as a byte literal. Returns an error if the input is
+ /// invalid or represents a different kind of literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ if input.is_empty() {
+ return Err(perr(None, Empty));
+ }
+ if !input.starts_with("b'") {
+ return Err(perr(None, InvalidByteLiteralStart));
+ }
+
+ let (value, start_suffix) = parse_impl(&input)?;
+ Ok(Self { raw: input, value, start_suffix })
+ }
+
+ /// Returns the byte value that this literal represents.
+ pub fn value(&self) -> u8 {
+ self.value
+ }
+
+ /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
+ pub fn suffix(&self) -> &str {
+ &(*self.raw)[self.start_suffix..]
+ }
+
+ /// Returns the raw input that was passed to `parse`.
+ pub fn raw_input(&self) -> &str {
+ &self.raw
+ }
+
+ /// Returns the raw input that was passed to `parse`, potentially owned.
+ pub fn into_raw_input(self) -> B {
+ self.raw
+ }
+
+}
+
+impl ByteLit<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn to_owned(&self) -> ByteLit<String> {
+ ByteLit {
+ raw: self.raw.to_owned(),
+ start_suffix: self.start_suffix,
+ value: self.value,
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for ByteLit<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad(&self.raw)
+ }
+}
+
+/// Precondition: must start with `b'`.
+#[inline(never)]
+pub(crate) fn parse_impl(input: &str) -> Result<(u8, usize), ParseError> {
+ let input_bytes = input.as_bytes();
+ let first = input_bytes.get(2).ok_or(perr(None, UnterminatedByteLiteral))?;
+ let (c, len) = match first {
+ b'\'' if input_bytes.get(3) == Some(&b'\'') => return Err(perr(2, UnescapedSingleQuote)),
+ b'\'' => return Err(perr(None, EmptyByteLiteral)),
+ b'\n' | b'\t' | b'\r' => return Err(perr(2, UnescapedSpecialWhitespace)),
+ b'\\' => unescape::<u8>(&input[2..], 2)?,
+ other if other.is_ascii() => (*other, 1),
+ _ => return Err(perr(2, NonAsciiInByteLiteral)),
+ };
+
+ match input[2 + len..].find('\'') {
+ Some(0) => {}
+ Some(_) => return Err(perr(None, OverlongByteLiteral)),
+ None => return Err(perr(None, UnterminatedByteLiteral)),
+ }
+
+ let start_suffix = 2 + len + 1;
+ let suffix = &input[start_suffix..];
+ check_suffix(suffix).map_err(|kind| perr(start_suffix, kind))?;
+
+ Ok((c, start_suffix))
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/byte/tests.rs b/third_party/rust/litrs/src/byte/tests.rs
new file mode 100644
index 0000000000..3cf16b5fc2
--- /dev/null
+++ b/third_party/rust/litrs/src/byte/tests.rs
@@ -0,0 +1,188 @@
+use crate::{ByteLit, Literal, test_util::{assert_parse_ok_eq, assert_roundtrip}};
+
+// ===== Utility functions =======================================================================
+
+macro_rules! check {
+ ($lit:literal) => { check!($lit, stringify!($lit), "") };
+ ($lit:literal, $input:expr, $suffix:literal) => {
+ let input = $input;
+ let expected = ByteLit {
+ raw: input,
+ start_suffix: input.len() - $suffix.len(),
+ value: $lit,
+ };
+
+ assert_parse_ok_eq(input, ByteLit::parse(input), expected.clone(), "ByteLit::parse");
+ assert_parse_ok_eq(input, Literal::parse(input), Literal::Byte(expected), "Literal::parse");
+ let lit = ByteLit::parse(input).unwrap();
+ assert_eq!(lit.value(), $lit);
+ assert_eq!(lit.suffix(), $suffix);
+ assert_roundtrip(expected.to_owned(), input);
+ };
+}
+
+
+// ===== Actual tests ============================================================================
+
+#[test]
+fn alphanumeric() {
+ check!(b'a');
+ check!(b'b');
+ check!(b'y');
+ check!(b'z');
+ check!(b'A');
+ check!(b'B');
+ check!(b'Y');
+ check!(b'Z');
+
+ check!(b'0');
+ check!(b'1');
+ check!(b'8');
+ check!(b'9');
+}
+
+#[test]
+fn special_chars() {
+ check!(b' ');
+ check!(b'!');
+ check!(b'"');
+ check!(b'#');
+ check!(b'$');
+ check!(b'%');
+ check!(b'&');
+ check!(b'(');
+ check!(b')');
+ check!(b'*');
+ check!(b'+');
+ check!(b',');
+ check!(b'-');
+ check!(b'.');
+ check!(b'/');
+ check!(b':');
+ check!(b';');
+ check!(b'<');
+ check!(b'=');
+ check!(b'>');
+ check!(b'?');
+ check!(b'@');
+ check!(b'[');
+ check!(b']');
+ check!(b'^');
+ check!(b'_');
+ check!(b'`');
+ check!(b'{');
+ check!(b'|');
+ check!(b'}');
+ check!(b'~');
+}
+
+#[test]
+fn quote_escapes() {
+ check!(b'\'');
+ check!(b'\"');
+}
+
+#[test]
+fn ascii_escapes() {
+ check!(b'\n');
+ check!(b'\r');
+ check!(b'\t');
+ check!(b'\\');
+ check!(b'\0');
+
+ check!(b'\x00');
+ check!(b'\x01');
+ check!(b'\x0c');
+ check!(b'\x0D');
+ check!(b'\x13');
+ check!(b'\x30');
+ check!(b'\x30');
+ check!(b'\x4B');
+ check!(b'\x6b');
+ check!(b'\x7F');
+ check!(b'\x7f');
+}
+
+#[test]
+fn byte_escapes() {
+ check!(b'\x80');
+ check!(b'\x8a');
+ check!(b'\x8C');
+ check!(b'\x99');
+ check!(b'\xa0');
+ check!(b'\xAd');
+ check!(b'\xfe');
+ check!(b'\xFe');
+ check!(b'\xfF');
+ check!(b'\xFF');
+}
+
+#[test]
+fn suffixes() {
+ check!(b'a', r##"b'a'peter"##, "peter");
+ check!(b'#', r##"b'#'peter"##, "peter");
+ check!(b'\n', r##"b'\n'peter"##, "peter");
+ check!(b'\'', r##"b'\''peter"##, "peter");
+ check!(b'\"', r##"b'\"'peter"##, "peter");
+ check!(b'\xFF', r##"b'\xFF'peter"##, "peter");
+}
+
+#[test]
+fn invald_escapes() {
+ assert_err!(ByteLit, r"b'\a'", UnknownEscape, 2..4);
+ assert_err!(ByteLit, r"b'\y'", UnknownEscape, 2..4);
+ assert_err!(ByteLit, r"b'\", UnterminatedEscape, 2..3);
+ assert_err!(ByteLit, r"b'\x'", UnterminatedEscape, 2..5);
+ assert_err!(ByteLit, r"b'\x1'", InvalidXEscape, 2..6);
+ assert_err!(ByteLit, r"b'\xaj'", InvalidXEscape, 2..6);
+ assert_err!(ByteLit, r"b'\xjb'", InvalidXEscape, 2..6);
+}
+
+#[test]
+fn unicode_escape_not_allowed() {
+ assert_err!(ByteLit, r"b'\u{0}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{00}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{b}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{B}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{7e}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{E4}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{e4}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{fc}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{Fc}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{fC}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{FC}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{b10}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{B10}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{0b10}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{2764}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{1f602}'", UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteLit, r"b'\u{1F602}'", UnicodeEscapeInByteLiteral, 2..4);
+}
+
+#[test]
+fn parse_err() {
+ assert_err!(ByteLit, r"b''", EmptyByteLiteral, None);
+ assert_err!(ByteLit, r"b' ''", UnexpectedChar, 4..5);
+
+ assert_err!(ByteLit, r"b'", UnterminatedByteLiteral, None);
+ assert_err!(ByteLit, r"b'a", UnterminatedByteLiteral, None);
+ assert_err!(ByteLit, r"b'\n", UnterminatedByteLiteral, None);
+ assert_err!(ByteLit, r"b'\x35", UnterminatedByteLiteral, None);
+
+ assert_err!(ByteLit, r"b'ab'", OverlongByteLiteral, None);
+ assert_err!(ByteLit, r"b'a _'", OverlongByteLiteral, None);
+ assert_err!(ByteLit, r"b'\n3'", OverlongByteLiteral, None);
+
+ assert_err!(ByteLit, r"", Empty, None);
+
+ assert_err!(ByteLit, r"b'''", UnescapedSingleQuote, 2);
+ assert_err!(ByteLit, r"b''''", UnescapedSingleQuote, 2);
+
+ assert_err!(ByteLit, "b'\n'", UnescapedSpecialWhitespace, 2);
+ assert_err!(ByteLit, "b'\t'", UnescapedSpecialWhitespace, 2);
+ assert_err!(ByteLit, "b'\r'", UnescapedSpecialWhitespace, 2);
+
+ assert_err!(ByteLit, "b'న'", NonAsciiInByteLiteral, 2);
+ assert_err!(ByteLit, "b'犬'", NonAsciiInByteLiteral, 2);
+ assert_err!(ByteLit, "b'🦊'", NonAsciiInByteLiteral, 2);
+}
diff --git a/third_party/rust/litrs/src/bytestr/mod.rs b/third_party/rust/litrs/src/bytestr/mod.rs
new file mode 100644
index 0000000000..a0e09727f4
--- /dev/null
+++ b/third_party/rust/litrs/src/bytestr/mod.rs
@@ -0,0 +1,126 @@
+use std::{fmt, ops::Range};
+
+use crate::{
+ Buffer, ParseError,
+ err::{perr, ParseErrorKind::*},
+ escape::{scan_raw_string, unescape_string},
+};
+
+
+/// A byte string or raw byte string literal, e.g. `b"hello"` or `br#"abc"def"#`.
+///
+/// See [the reference][ref] for more information.
+///
+/// [ref]: https://doc.rust-lang.org/reference/tokens.html#byte-string-literals
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ByteStringLit<B: Buffer> {
+ /// The raw input.
+ raw: B,
+
+ /// The string value (with all escaped unescaped), or `None` if there were
+ /// no escapes. In the latter case, `input` is the string value.
+ value: Option<Vec<u8>>,
+
+ /// The number of hash signs in case of a raw string literal, or `None` if
+ /// it's not a raw string literal.
+ num_hashes: Option<u32>,
+
+ /// Start index of the suffix or `raw.len()` if there is no suffix.
+ start_suffix: usize,
+}
+
+impl<B: Buffer> ByteStringLit<B> {
+ /// Parses the input as a (raw) byte string literal. Returns an error if the
+ /// input is invalid or represents a different kind of literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ if input.is_empty() {
+ return Err(perr(None, Empty));
+ }
+ if !input.starts_with(r#"b""#) && !input.starts_with("br") {
+ return Err(perr(None, InvalidByteStringLiteralStart));
+ }
+
+ let (value, num_hashes, start_suffix) = parse_impl(&input)?;
+ Ok(Self { raw: input, value, num_hashes, start_suffix })
+ }
+
+ /// Returns the string value this literal represents (where all escapes have
+ /// been turned into their respective values).
+ pub fn value(&self) -> &[u8] {
+ self.value.as_deref().unwrap_or(&self.raw.as_bytes()[self.inner_range()])
+ }
+
+ /// Like `value` but returns a potentially owned version of the value.
+ ///
+ /// The return value is either `Cow<'static, [u8]>` if `B = String`, or
+ /// `Cow<'a, [u8]>` if `B = &'a str`.
+ pub fn into_value(self) -> B::ByteCow {
+ let inner_range = self.inner_range();
+ let Self { raw, value, .. } = self;
+ value.map(B::ByteCow::from).unwrap_or_else(|| raw.cut(inner_range).into_byte_cow())
+ }
+
+ /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
+ pub fn suffix(&self) -> &str {
+ &(*self.raw)[self.start_suffix..]
+ }
+
+ /// Returns whether this literal is a raw string literal (starting with
+ /// `r`).
+ pub fn is_raw_byte_string(&self) -> bool {
+ self.num_hashes.is_some()
+ }
+
+ /// Returns the raw input that was passed to `parse`.
+ pub fn raw_input(&self) -> &str {
+ &self.raw
+ }
+
+ /// Returns the raw input that was passed to `parse`, potentially owned.
+ pub fn into_raw_input(self) -> B {
+ self.raw
+ }
+
+ /// The range within `self.raw` that excludes the quotes and potential `r#`.
+ fn inner_range(&self) -> Range<usize> {
+ match self.num_hashes {
+ None => 2..self.start_suffix - 1,
+ Some(n) => 2 + n as usize + 1..self.start_suffix - n as usize - 1,
+ }
+ }
+}
+
+impl ByteStringLit<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn into_owned(self) -> ByteStringLit<String> {
+ ByteStringLit {
+ raw: self.raw.to_owned(),
+ value: self.value,
+ num_hashes: self.num_hashes,
+ start_suffix: self.start_suffix,
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for ByteStringLit<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad(&self.raw)
+ }
+}
+
+
+/// Precondition: input has to start with either `b"` or `br`.
+#[inline(never)]
+fn parse_impl(input: &str) -> Result<(Option<Vec<u8>>, Option<u32>, usize), ParseError> {
+ if input.starts_with("br") {
+ scan_raw_string::<u8>(&input, 2)
+ .map(|(v, num, start_suffix)| (v.map(String::into_bytes), Some(num), start_suffix))
+ } else {
+ unescape_string::<u8>(&input, 2)
+ .map(|(v, start_suffix)| (v.map(String::into_bytes), None, start_suffix))
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/bytestr/tests.rs b/third_party/rust/litrs/src/bytestr/tests.rs
new file mode 100644
index 0000000000..2afef5a99c
--- /dev/null
+++ b/third_party/rust/litrs/src/bytestr/tests.rs
@@ -0,0 +1,224 @@
+use crate::{Literal, ByteStringLit, test_util::{assert_parse_ok_eq, assert_roundtrip}};
+
+// ===== Utility functions =======================================================================
+
+macro_rules! check {
+ ($lit:literal, $has_escapes:expr, $num_hashes:expr) => {
+ check!($lit, stringify!($lit), $has_escapes, $num_hashes, "")
+ };
+ ($lit:literal, $input:expr, $has_escapes:expr, $num_hashes:expr, $suffix:literal) => {
+ let input = $input;
+ let expected = ByteStringLit {
+ raw: input,
+ value: if $has_escapes { Some($lit.to_vec()) } else { None },
+ num_hashes: $num_hashes,
+ start_suffix: input.len() - $suffix.len(),
+ };
+
+ assert_parse_ok_eq(
+ input, ByteStringLit::parse(input), expected.clone(), "ByteStringLit::parse");
+ assert_parse_ok_eq(
+ input, Literal::parse(input), Literal::ByteString(expected.clone()), "Literal::parse");
+ let lit = ByteStringLit::parse(input).unwrap();
+ assert_eq!(lit.value(), $lit);
+ assert_eq!(lit.suffix(), $suffix);
+ assert_eq!(lit.into_value().as_ref(), $lit);
+ assert_roundtrip(expected.into_owned(), input);
+ };
+}
+
+
+// ===== Actual tests ============================================================================
+
+#[test]
+fn simple() {
+ check!(b"", false, None);
+ check!(b"a", false, None);
+ check!(b"peter", false, None);
+}
+
+#[test]
+fn special_whitespace() {
+ let strings = ["\n", "\t", "foo\tbar", "baz\n"];
+
+ for &s in &strings {
+ let input = format!(r#"b"{}""#, s);
+ let input_raw = format!(r#"br"{}""#, s);
+ for (input, num_hashes) in vec![(input, None), (input_raw, Some(0))] {
+ let expected = ByteStringLit {
+ raw: &*input,
+ value: None,
+ num_hashes,
+ start_suffix: input.len(),
+ };
+ assert_parse_ok_eq(
+ &input, ByteStringLit::parse(&*input), expected.clone(), "ByteStringLit::parse");
+ assert_parse_ok_eq(
+ &input, Literal::parse(&*input), Literal::ByteString(expected), "Literal::parse");
+ assert_eq!(ByteStringLit::parse(&*input).unwrap().value(), s.as_bytes());
+ assert_eq!(ByteStringLit::parse(&*input).unwrap().into_value(), s.as_bytes());
+ }
+ }
+
+ let res = ByteStringLit::parse("br\"\r\"").expect("failed to parse");
+ assert_eq!(res.value(), b"\r");
+}
+
+#[test]
+fn simple_escapes() {
+ check!(b"a\nb", true, None);
+ check!(b"\nb", true, None);
+ check!(b"a\n", true, None);
+ check!(b"\n", true, None);
+
+ check!(b"\x60foo \t bar\rbaz\n banana \0kiwi", true, None);
+ check!(b"foo \\ferris", true, None);
+ check!(b"baz \\ferris\"box", true, None);
+ check!(b"\\foo\\ banana\" baz\"", true, None);
+ check!(b"\"foo \\ferris \" baz\\", true, None);
+
+ check!(b"\x00", true, None);
+ check!(b" \x01", true, None);
+ check!(b"\x0c foo", true, None);
+ check!(b" foo\x0D ", true, None);
+ check!(b"\\x13", true, None);
+ check!(b"\"x30", true, None);
+}
+
+#[test]
+fn string_continue() {
+ check!(b"foo\
+ bar", true, None);
+ check!(b"foo\
+bar", true, None);
+
+ check!(b"foo\
+
+ banana", true, None);
+
+ // Weird whitespace characters
+ let lit = ByteStringLit::parse("b\"foo\\\n\r\t\n \n\tbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), b"foobar");
+
+ // Raw strings do not handle "string continues"
+ check!(br"foo\
+ bar", false, Some(0));
+}
+
+#[test]
+fn crlf_newlines() {
+ let lit = ByteStringLit::parse("b\"foo\r\nbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), b"foo\nbar");
+
+ let lit = ByteStringLit::parse("b\"\r\nbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), b"\nbar");
+
+ let lit = ByteStringLit::parse("b\"foo\r\n\"").expect("failed to parse");
+ assert_eq!(lit.value(), b"foo\n");
+
+ let lit = ByteStringLit::parse("br\"foo\r\nbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), b"foo\nbar");
+
+ let lit = ByteStringLit::parse("br#\"\r\nbar\"#").expect("failed to parse");
+ assert_eq!(lit.value(), b"\nbar");
+
+ let lit = ByteStringLit::parse("br##\"foo\r\n\"##").expect("failed to parse");
+ assert_eq!(lit.value(), b"foo\n");
+}
+
+#[test]
+fn raw_byte_string() {
+ check!(br"", false, Some(0));
+ check!(br"a", false, Some(0));
+ check!(br"peter", false, Some(0));
+ check!(br"Greetings jason!", false, Some(0));
+
+ check!(br#""#, false, Some(1));
+ check!(br#"a"#, false, Some(1));
+ check!(br##"peter"##, false, Some(2));
+ check!(br###"Greetings # Jason!"###, false, Some(3));
+ check!(br########"we ## need #### more ####### hashtags"########, false, Some(8));
+
+ check!(br#"foo " bar"#, false, Some(1));
+ check!(br##"foo " bar"##, false, Some(2));
+ check!(br#"foo """" '"'" bar"#, false, Some(1));
+ check!(br#""foo""#, false, Some(1));
+ check!(br###""foo'"###, false, Some(3));
+ check!(br#""x'#_#s'"#, false, Some(1));
+ check!(br"#", false, Some(0));
+ check!(br"foo#", false, Some(0));
+ check!(br"##bar", false, Some(0));
+ check!(br###""##foo"##bar'"###, false, Some(3));
+
+ check!(br"foo\n\t\r\0\\x60\u{123}doggo", false, Some(0));
+ check!(br#"cat\n\t\r\0\\x60\u{123}doggo"#, false, Some(1));
+}
+
+#[test]
+fn suffixes() {
+ check!(b"hello", r###"b"hello"suffix"###, false, None, "suffix");
+ check!(b"fox", r#"b"fox"peter"#, false, None, "peter");
+ check!(b"a\x0cb\\", r#"b"a\x0cb\\"_jürgen"#, true, None, "_jürgen");
+ check!(br"a\x0cb\\", r###"br#"a\x0cb\\"#_jürgen"###, false, Some(1), "_jürgen");
+}
+
+#[test]
+fn parse_err() {
+ assert_err!(ByteStringLit, r#"b""#, UnterminatedString, None);
+ assert_err!(ByteStringLit, r#"b"cat"#, UnterminatedString, None);
+ assert_err!(ByteStringLit, r#"b"Jurgen"#, UnterminatedString, None);
+ assert_err!(ByteStringLit, r#"b"foo bar baz"#, UnterminatedString, None);
+
+ assert_err!(ByteStringLit, r#"b"fox"peter""#, InvalidSuffix, 6);
+ assert_err!(ByteStringLit, r###"br#"foo "# bar"#"###, UnexpectedChar, 10);
+
+ assert_err!(ByteStringLit, "b\"\r\"", IsolatedCr, 2);
+ assert_err!(ByteStringLit, "b\"fo\rx\"", IsolatedCr, 4);
+
+ assert_err!(ByteStringLit, r##"br####""##, UnterminatedRawString, None);
+ assert_err!(ByteStringLit, r#####"br##"foo"#bar"#####, UnterminatedRawString, None);
+ assert_err!(ByteStringLit, r##"br####"##, InvalidLiteral, None);
+ assert_err!(ByteStringLit, r##"br####x"##, InvalidLiteral, None);
+}
+
+#[test]
+fn non_ascii() {
+ assert_err!(ByteStringLit, r#"b"న""#, NonAsciiInByteLiteral, 2);
+ assert_err!(ByteStringLit, r#"b"foo犬""#, NonAsciiInByteLiteral, 5);
+ assert_err!(ByteStringLit, r#"b"x🦊baz""#, NonAsciiInByteLiteral, 3);
+ assert_err!(ByteStringLit, r#"br"న""#, NonAsciiInByteLiteral, 3);
+ assert_err!(ByteStringLit, r#"br"foo犬""#, NonAsciiInByteLiteral, 6);
+ assert_err!(ByteStringLit, r#"br"x🦊baz""#, NonAsciiInByteLiteral, 4);
+}
+
+#[test]
+fn invalid_escapes() {
+ assert_err!(ByteStringLit, r#"b"\a""#, UnknownEscape, 2..4);
+ assert_err!(ByteStringLit, r#"b"foo\y""#, UnknownEscape, 5..7);
+ assert_err!(ByteStringLit, r#"b"\"#, UnterminatedEscape, 2);
+ assert_err!(ByteStringLit, r#"b"\x""#, UnterminatedEscape, 2..4);
+ assert_err!(ByteStringLit, r#"b"foo\x1""#, UnterminatedEscape, 5..8);
+ assert_err!(ByteStringLit, r#"b" \xaj""#, InvalidXEscape, 3..7);
+ assert_err!(ByteStringLit, r#"b"\xjbbaz""#, InvalidXEscape, 2..6);
+}
+
+#[test]
+fn unicode_escape_not_allowed() {
+ assert_err!(ByteStringLit, r#"b"\u{0}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{00}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{b}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{B}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{7e}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{E4}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{e4}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{fc}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{Fc}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{fC}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{FC}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{b10}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{B10}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{0b10}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{2764}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{1f602}""#, UnicodeEscapeInByteLiteral, 2..4);
+ assert_err!(ByteStringLit, r#"b"\u{1F602}""#, UnicodeEscapeInByteLiteral, 2..4);
+}
diff --git a/third_party/rust/litrs/src/char/mod.rs b/third_party/rust/litrs/src/char/mod.rs
new file mode 100644
index 0000000000..54f6f1137f
--- /dev/null
+++ b/third_party/rust/litrs/src/char/mod.rs
@@ -0,0 +1,105 @@
+use std::fmt;
+
+use crate::{
+ Buffer, ParseError,
+ err::{perr, ParseErrorKind::*},
+ escape::unescape,
+ parse::{first_byte_or_empty, check_suffix},
+};
+
+
+/// A character literal, e.g. `'g'` or `'🦊'`.
+///
+/// See [the reference][ref] for more information.
+///
+/// [ref]: https://doc.rust-lang.org/reference/tokens.html#character-literals
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct CharLit<B: Buffer> {
+ raw: B,
+ /// Start index of the suffix or `raw.len()` if there is no suffix.
+ start_suffix: usize,
+ value: char,
+}
+
+impl<B: Buffer> CharLit<B> {
+ /// Parses the input as a character literal. Returns an error if the input
+ /// is invalid or represents a different kind of literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ match first_byte_or_empty(&input)? {
+ b'\'' => {
+ let (value, start_suffix) = parse_impl(&input)?;
+ Ok(Self { raw: input, value, start_suffix })
+ },
+ _ => Err(perr(0, DoesNotStartWithQuote)),
+ }
+ }
+
+ /// Returns the character value that this literal represents.
+ pub fn value(&self) -> char {
+ self.value
+ }
+
+ /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
+ pub fn suffix(&self) -> &str {
+ &(*self.raw)[self.start_suffix..]
+ }
+
+ /// Returns the raw input that was passed to `parse`.
+ pub fn raw_input(&self) -> &str {
+ &self.raw
+ }
+
+ /// Returns the raw input that was passed to `parse`, potentially owned.
+ pub fn into_raw_input(self) -> B {
+ self.raw
+ }
+
+}
+
+impl CharLit<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn to_owned(&self) -> CharLit<String> {
+ CharLit {
+ raw: self.raw.to_owned(),
+ start_suffix: self.start_suffix,
+ value: self.value,
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for CharLit<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad(&self.raw)
+ }
+}
+
+/// Precondition: first character in input must be `'`.
+#[inline(never)]
+pub(crate) fn parse_impl(input: &str) -> Result<(char, usize), ParseError> {
+ let first = input.chars().nth(1).ok_or(perr(None, UnterminatedCharLiteral))?;
+ let (c, len) = match first {
+ '\'' if input.chars().nth(2) == Some('\'') => return Err(perr(1, UnescapedSingleQuote)),
+ '\'' => return Err(perr(None, EmptyCharLiteral)),
+ '\n' | '\t' | '\r'
+ => return Err(perr(1, UnescapedSpecialWhitespace)),
+
+ '\\' => unescape::<char>(&input[1..], 1)?,
+ other => (other, other.len_utf8()),
+ };
+
+ match input[1 + len..].find('\'') {
+ Some(0) => {}
+ Some(_) => return Err(perr(None, OverlongCharLiteral)),
+ None => return Err(perr(None, UnterminatedCharLiteral)),
+ }
+
+ let start_suffix = 1 + len + 1;
+ let suffix = &input[start_suffix..];
+ check_suffix(suffix).map_err(|kind| perr(start_suffix, kind))?;
+
+ Ok((c, start_suffix))
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/char/tests.rs b/third_party/rust/litrs/src/char/tests.rs
new file mode 100644
index 0000000000..19219db73b
--- /dev/null
+++ b/third_party/rust/litrs/src/char/tests.rs
@@ -0,0 +1,227 @@
+use crate::{Literal, test_util::{assert_parse_ok_eq, assert_roundtrip}};
+use super::CharLit;
+
+// ===== Utility functions =======================================================================
+
+macro_rules! check {
+ ($lit:literal) => { check!($lit, stringify!($lit), "") };
+ ($lit:literal, $input:expr, $suffix:literal) => {
+ let input = $input;
+ let expected = CharLit {
+ raw: input,
+ start_suffix: input.len() - $suffix.len(),
+ value: $lit,
+ };
+
+ assert_parse_ok_eq(input, CharLit::parse(input), expected.clone(), "CharLit::parse");
+ assert_parse_ok_eq(input, Literal::parse(input), Literal::Char(expected), "Literal::parse");
+ let lit = CharLit::parse(input).unwrap();
+ assert_eq!(lit.value(), $lit);
+ assert_eq!(lit.suffix(), $suffix);
+ assert_roundtrip(expected.to_owned(), input);
+ };
+}
+
+
+// ===== Actual tests ============================================================================
+
+#[test]
+fn alphanumeric() {
+ check!('a');
+ check!('b');
+ check!('y');
+ check!('z');
+ check!('A');
+ check!('B');
+ check!('Y');
+ check!('Z');
+
+ check!('0');
+ check!('1');
+ check!('8');
+ check!('9');
+}
+
+#[test]
+fn special_chars() {
+ check!(' ');
+ check!('!');
+ check!('"');
+ check!('#');
+ check!('$');
+ check!('%');
+ check!('&');
+ check!('(');
+ check!(')');
+ check!('*');
+ check!('+');
+ check!(',');
+ check!('-');
+ check!('.');
+ check!('/');
+ check!(':');
+ check!(';');
+ check!('<');
+ check!('=');
+ check!('>');
+ check!('?');
+ check!('@');
+ check!('[');
+ check!(']');
+ check!('^');
+ check!('_');
+ check!('`');
+ check!('{');
+ check!('|');
+ check!('}');
+ check!('~');
+}
+
+#[test]
+fn unicode() {
+ check!('న');
+ check!('犬');
+ check!('🦊');
+}
+
+#[test]
+fn quote_escapes() {
+ check!('\'');
+ check!('\"');
+}
+
+#[test]
+fn ascii_escapes() {
+ check!('\n');
+ check!('\r');
+ check!('\t');
+ check!('\\');
+ check!('\0');
+
+ check!('\x00');
+ check!('\x01');
+ check!('\x0c');
+ check!('\x0D');
+ check!('\x13');
+ check!('\x30');
+ check!('\x30');
+ check!('\x4B');
+ check!('\x6b');
+ check!('\x7F');
+ check!('\x7f');
+}
+
+#[test]
+fn unicode_escapes() {
+ check!('\u{0}');
+ check!('\u{00}');
+ check!('\u{b}');
+ check!('\u{B}');
+ check!('\u{7e}');
+ check!('\u{E4}');
+ check!('\u{e4}');
+ check!('\u{fc}');
+ check!('\u{Fc}');
+ check!('\u{fC}');
+ check!('\u{FC}');
+ check!('\u{b10}');
+ check!('\u{B10}');
+ check!('\u{0b10}');
+ check!('\u{2764}');
+ check!('\u{1f602}');
+ check!('\u{1F602}');
+
+ check!('\u{0}');
+ check!('\u{0__}');
+ check!('\u{3_b}');
+ check!('\u{1_F_6_0_2}');
+ check!('\u{1_F6_02_____}');
+}
+
+#[test]
+fn suffixes() {
+ check!('a', r##"'a'peter"##, "peter");
+ check!('#', r##"'#'peter"##, "peter");
+ check!('\n', r##"'\n'peter"##, "peter");
+ check!('\'', r##"'\''peter"##, "peter");
+ check!('\"', r##"'\"'peter"##, "peter");
+}
+
+#[test]
+fn invald_ascii_escapes() {
+ assert_err!(CharLit, r"'\x80'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\x81'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\x8a'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\x8F'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xa0'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xB0'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xc3'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xDf'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xff'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xfF'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xFf'", NonAsciiXEscape, 1..5);
+ assert_err!(CharLit, r"'\xFF'", NonAsciiXEscape, 1..5);
+}
+
+#[test]
+fn invalid_escapes() {
+ assert_err!(CharLit, r"'\a'", UnknownEscape, 1..3);
+ assert_err!(CharLit, r"'\y'", UnknownEscape, 1..3);
+ assert_err!(CharLit, r"'\", UnterminatedEscape, 1);
+ assert_err!(CharLit, r"'\x'", UnterminatedEscape, 1..4);
+ assert_err!(CharLit, r"'\x1'", InvalidXEscape, 1..5);
+ assert_err!(CharLit, r"'\xaj'", InvalidXEscape, 1..5);
+ assert_err!(CharLit, r"'\xjb'", InvalidXEscape, 1..5);
+}
+
+#[test]
+fn invalid_unicode_escapes() {
+ assert_err!(CharLit, r"'\u'", UnicodeEscapeWithoutBrace, 1..3);
+ assert_err!(CharLit, r"'\u '", UnicodeEscapeWithoutBrace, 1..3);
+ assert_err!(CharLit, r"'\u3'", UnicodeEscapeWithoutBrace, 1..3);
+
+ assert_err!(CharLit, r"'\u{'", UnterminatedUnicodeEscape, 1..5);
+ assert_err!(CharLit, r"'\u{12'", UnterminatedUnicodeEscape, 1..7);
+ assert_err!(CharLit, r"'\u{a0b'", UnterminatedUnicodeEscape, 1..8);
+ assert_err!(CharLit, r"'\u{a0_b '", UnterminatedUnicodeEscape, 1..11);
+
+ assert_err!(CharLit, r"'\u{_}'", InvalidStartOfUnicodeEscape, 4);
+ assert_err!(CharLit, r"'\u{_5f}'", InvalidStartOfUnicodeEscape, 4);
+
+ assert_err!(CharLit, r"'\u{x}'", NonHexDigitInUnicodeEscape, 4);
+ assert_err!(CharLit, r"'\u{0x}'", NonHexDigitInUnicodeEscape, 5);
+ assert_err!(CharLit, r"'\u{3bx}'", NonHexDigitInUnicodeEscape, 6);
+ assert_err!(CharLit, r"'\u{3b_x}'", NonHexDigitInUnicodeEscape, 7);
+ assert_err!(CharLit, r"'\u{4x_}'", NonHexDigitInUnicodeEscape, 5);
+
+ assert_err!(CharLit, r"'\u{1234567}'", TooManyDigitInUnicodeEscape, 10);
+ assert_err!(CharLit, r"'\u{1234567}'", TooManyDigitInUnicodeEscape, 10);
+ assert_err!(CharLit, r"'\u{1_23_4_56_7}'", TooManyDigitInUnicodeEscape, 14);
+ assert_err!(CharLit, r"'\u{abcdef123}'", TooManyDigitInUnicodeEscape, 10);
+
+ assert_err!(CharLit, r"'\u{110000}'", InvalidUnicodeEscapeChar, 1..10);
+}
+
+#[test]
+fn parse_err() {
+ assert_err!(CharLit, r"''", EmptyCharLiteral, None);
+ assert_err!(CharLit, r"' ''", UnexpectedChar, 3);
+
+ assert_err!(CharLit, r"'", UnterminatedCharLiteral, None);
+ assert_err!(CharLit, r"'a", UnterminatedCharLiteral, None);
+ assert_err!(CharLit, r"'\n", UnterminatedCharLiteral, None);
+ assert_err!(CharLit, r"'\x35", UnterminatedCharLiteral, None);
+
+ assert_err!(CharLit, r"'ab'", OverlongCharLiteral, None);
+ assert_err!(CharLit, r"'a _'", OverlongCharLiteral, None);
+ assert_err!(CharLit, r"'\n3'", OverlongCharLiteral, None);
+
+ assert_err!(CharLit, r"", Empty, None);
+
+ assert_err!(CharLit, r"'''", UnescapedSingleQuote, 1);
+ assert_err!(CharLit, r"''''", UnescapedSingleQuote, 1);
+
+ assert_err!(CharLit, "'\n'", UnescapedSpecialWhitespace, 1);
+ assert_err!(CharLit, "'\t'", UnescapedSpecialWhitespace, 1);
+ assert_err!(CharLit, "'\r'", UnescapedSpecialWhitespace, 1);
+}
diff --git a/third_party/rust/litrs/src/err.rs b/third_party/rust/litrs/src/err.rs
new file mode 100644
index 0000000000..86d51dc4a8
--- /dev/null
+++ b/third_party/rust/litrs/src/err.rs
@@ -0,0 +1,371 @@
+use std::{fmt, ops::Range};
+
+
+/// An error signaling that a different kind of token was expected. Returned by
+/// the various `TryFrom` impls.
+#[derive(Debug, Clone, Copy)]
+pub struct InvalidToken {
+ pub(crate) expected: TokenKind,
+ pub(crate) actual: TokenKind,
+ pub(crate) span: Span,
+}
+
+impl InvalidToken {
+ /// Returns a token stream representing `compile_error!("msg");` where
+ /// `"msg"` is the output of `self.to_string()`. **Panics if called outside
+ /// of a proc-macro context!**
+ pub fn to_compile_error(&self) -> proc_macro::TokenStream {
+ use proc_macro::{Delimiter, Ident, Group, Punct, Spacing, TokenTree};
+
+ let span = match self.span {
+ Span::One(s) => s,
+ #[cfg(feature = "proc-macro2")]
+ Span::Two(s) => s.unwrap(),
+ };
+ let msg = self.to_string();
+ let tokens = vec![
+ TokenTree::from(Ident::new("compile_error", span)),
+ TokenTree::from(Punct::new('!', Spacing::Alone)),
+ TokenTree::from(Group::new(
+ Delimiter::Parenthesis,
+ TokenTree::from(proc_macro::Literal::string(&msg)).into(),
+ )),
+ ];
+
+
+ tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect()
+ }
+
+ /// Like [`to_compile_error`][Self::to_compile_error], but returns a token
+ /// stream from `proc_macro2` and does not panic outside of a proc-macro
+ /// context.
+ #[cfg(feature = "proc-macro2")]
+ pub fn to_compile_error2(&self) -> proc_macro2::TokenStream {
+ use proc_macro2::{Delimiter, Ident, Group, Punct, Spacing, TokenTree};
+
+ let span = match self.span {
+ Span::One(s) => proc_macro2::Span::from(s),
+ Span::Two(s) => s,
+ };
+ let msg = self.to_string();
+ let tokens = vec![
+ TokenTree::from(Ident::new("compile_error", span)),
+ TokenTree::from(Punct::new('!', Spacing::Alone)),
+ TokenTree::from(Group::new(
+ Delimiter::Parenthesis,
+ TokenTree::from(proc_macro2::Literal::string(&msg)).into(),
+ )),
+ ];
+
+
+ tokens.into_iter().map(|mut t| { t.set_span(span); t }).collect()
+ }
+}
+
+impl std::error::Error for InvalidToken {}
+
+impl fmt::Display for InvalidToken {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fn kind_desc(kind: TokenKind) -> &'static str {
+ match kind {
+ TokenKind::Punct => "a punctuation character",
+ TokenKind::Ident => "an identifier",
+ TokenKind::Group => "a group",
+ TokenKind::Literal => "a literal",
+ TokenKind::BoolLit => "a bool literal (`true` or `false`)",
+ TokenKind::ByteLit => "a byte literal (e.g. `b'r')",
+ TokenKind::ByteStringLit => r#"a byte string literal (e.g. `b"fox"`)"#,
+ TokenKind::CharLit => "a character literal (e.g. `'P'`)",
+ TokenKind::FloatLit => "a float literal (e.g. `3.14`)",
+ TokenKind::IntegerLit => "an integer literal (e.g. `27`)",
+ TokenKind::StringLit => r#"a string literal (e.g. "Ferris")"#,
+ }
+ }
+
+ write!(f, "expected {}, but found {}", kind_desc(self.expected), kind_desc(self.actual))
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum TokenKind {
+ Punct,
+ Ident,
+ Group,
+ Literal,
+ BoolLit,
+ ByteLit,
+ ByteStringLit,
+ CharLit,
+ FloatLit,
+ IntegerLit,
+ StringLit,
+}
+
+/// Unfortunately, we have to deal with both cases.
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum Span {
+ One(proc_macro::Span),
+ #[cfg(feature = "proc-macro2")]
+ Two(proc_macro2::Span),
+}
+
+impl From<proc_macro::Span> for Span {
+ fn from(src: proc_macro::Span) -> Self {
+ Self::One(src)
+ }
+}
+
+#[cfg(feature = "proc-macro2")]
+impl From<proc_macro2::Span> for Span {
+ fn from(src: proc_macro2::Span) -> Self {
+ Self::Two(src)
+ }
+}
+
+/// Errors during parsing.
+///
+/// This type should be seen primarily for error reporting and not for catching
+/// specific cases. The span and error kind are not guaranteed to be stable
+/// over different versions of this library, meaning that a returned error can
+/// change from one version to the next. There are simply too many fringe cases
+/// that are not easy to classify as a specific error kind. It depends entirely
+/// on the specific parser code how an invalid input is categorized.
+///
+/// Consider these examples:
+/// - `'\` can be seen as
+/// - invalid escape in character literal, or
+/// - unterminated character literal.
+/// - `'''` can be seen as
+/// - empty character literal, or
+/// - unescaped quote character in character literal.
+/// - `0b64` can be seen as
+/// - binary integer literal with invalid digit 6, or
+/// - binary integer literal with invalid digit 4, or
+/// - decimal integer literal with invalid digit b, or
+/// - decimal integer literal 0 with unknown type suffix `b64`.
+///
+/// If you want to see more if these examples, feel free to check out the unit
+/// tests of this library.
+///
+/// While this library does its best to emit sensible and precise errors, and to
+/// keep the returned errors as stable as possible, full stability cannot be
+/// guaranteed.
+#[derive(Debug, Clone)]
+pub struct ParseError {
+ pub(crate) span: Option<Range<usize>>,
+ pub(crate) kind: ParseErrorKind,
+}
+
+impl ParseError {
+ /// Returns a span of this error, if available. **Note**: the returned span
+ /// might change in future versions of this library. See [the documentation
+ /// of this type][ParseError] for more information.
+ pub fn span(&self) -> Option<Range<usize>> {
+ self.span.clone()
+ }
+}
+
+/// This is a free standing function instead of an associated one to reduce
+/// noise around parsing code. There are lots of places that create errors, we
+/// I wanna keep them as short as possible.
+pub(crate) fn perr(span: impl SpanLike, kind: ParseErrorKind) -> ParseError {
+ ParseError {
+ span: span.into_span(),
+ kind,
+ }
+}
+
+pub(crate) trait SpanLike {
+ fn into_span(self) -> Option<Range<usize>>;
+}
+
+impl SpanLike for Option<Range<usize>> {
+ #[inline(always)]
+ fn into_span(self) -> Option<Range<usize>> {
+ self
+ }
+}
+impl SpanLike for Range<usize> {
+ #[inline(always)]
+ fn into_span(self) -> Option<Range<usize>> {
+ Some(self)
+ }
+}
+impl SpanLike for usize {
+ #[inline(always)]
+ fn into_span(self) -> Option<Range<usize>> {
+ Some(self..self + 1)
+ }
+}
+
+
+/// Kinds of errors.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[non_exhaustive]
+pub(crate) enum ParseErrorKind {
+ /// The input was an empty string
+ Empty,
+
+ /// An unexpected char was encountered.
+ UnexpectedChar,
+
+ /// Literal was not recognized.
+ InvalidLiteral,
+
+ /// Input does not start with decimal digit when trying to parse an integer.
+ DoesNotStartWithDigit,
+
+ /// A digit invalid for the specified integer base was found.
+ InvalidDigit,
+
+ /// Integer literal does not contain any valid digits.
+ NoDigits,
+
+ /// Exponent of a float literal does not contain any digits.
+ NoExponentDigits,
+
+ /// An unknown escape code, e.g. `\b`.
+ UnknownEscape,
+
+ /// A started escape sequence where the input ended before the escape was
+ /// finished.
+ UnterminatedEscape,
+
+ /// An `\x` escape where the two digits are not valid hex digits.
+ InvalidXEscape,
+
+ /// A string or character literal using the `\xNN` escape where `NN > 0x7F`.
+ NonAsciiXEscape,
+
+ /// A `\u{...}` escape in a byte or byte string literal.
+ UnicodeEscapeInByteLiteral,
+
+ /// A Unicode escape that does not start with a hex digit.
+ InvalidStartOfUnicodeEscape,
+
+ /// A `\u{...}` escape that lacks the opening brace.
+ UnicodeEscapeWithoutBrace,
+
+ /// In a `\u{...}` escape, a non-hex digit and non-underscore character was
+ /// found.
+ NonHexDigitInUnicodeEscape,
+
+ /// More than 6 digits found in unicode escape.
+ TooManyDigitInUnicodeEscape,
+
+ /// The value from a unicode escape does not represent a valid character.
+ InvalidUnicodeEscapeChar,
+
+ /// A `\u{..` escape that is not terminated (lacks the closing brace).
+ UnterminatedUnicodeEscape,
+
+ /// A character literal that's not terminated.
+ UnterminatedCharLiteral,
+
+ /// A character literal that contains more than one character.
+ OverlongCharLiteral,
+
+ /// An empty character literal, i.e. `''`.
+ EmptyCharLiteral,
+
+ UnterminatedByteLiteral,
+ OverlongByteLiteral,
+ EmptyByteLiteral,
+ NonAsciiInByteLiteral,
+
+ /// A `'` character was not escaped in a character or byte literal, or a `"`
+ /// character was not escaped in a string or byte string literal.
+ UnescapedSingleQuote,
+
+ /// A \n, \t or \r raw character in a char or byte literal.
+ UnescapedSpecialWhitespace,
+
+ /// When parsing a character, byte, string or byte string literal directly
+ /// and the input does not start with the corresponding quote character
+ /// (plus optional raw string prefix).
+ DoesNotStartWithQuote,
+
+ /// Unterminated raw string literal.
+ UnterminatedRawString,
+
+ /// String literal without a `"` at the end.
+ UnterminatedString,
+
+ /// Invalid start for a string literal.
+ InvalidStringLiteralStart,
+
+ /// Invalid start for a byte literal.
+ InvalidByteLiteralStart,
+
+ InvalidByteStringLiteralStart,
+
+ /// An literal `\r` character not followed by a `\n` character in a
+ /// (raw) string or byte string literal.
+ IsolatedCr,
+
+ /// Literal suffix is not a valid identifier.
+ InvalidSuffix,
+
+ /// Returned by `Float::parse` if an integer literal (no fractional nor
+ /// exponent part) is passed.
+ UnexpectedIntegerLit,
+
+ /// Integer suffixes cannot start with `e` or `E` as this conflicts with the
+ /// grammar for float literals.
+ IntegerSuffixStartingWithE,
+}
+
+impl std::error::Error for ParseError {}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use ParseErrorKind::*;
+
+ let description = match self.kind {
+ Empty => "input is empty",
+ UnexpectedChar => "unexpected character",
+ InvalidLiteral => "invalid literal",
+ DoesNotStartWithDigit => "number literal does not start with decimal digit",
+ InvalidDigit => "integer literal contains a digit invalid for its base",
+ NoDigits => "integer literal does not contain any digits",
+ NoExponentDigits => "exponent of floating point literal does not contain any digits",
+ UnknownEscape => "unknown escape",
+ UnterminatedEscape => "unterminated escape: input ended too soon",
+ InvalidXEscape => r"invalid `\x` escape: not followed by two hex digits",
+ NonAsciiXEscape => r"`\x` escape in char/string literal exceed ASCII range",
+ UnicodeEscapeInByteLiteral => r"`\u{...}` escape in byte (string) literal not allowed",
+ InvalidStartOfUnicodeEscape => r"invalid start of `\u{...}` escape",
+ UnicodeEscapeWithoutBrace => r"`Unicode \u{...}` escape without opening brace",
+ NonHexDigitInUnicodeEscape => r"non-hex digit found in `\u{...}` escape",
+ TooManyDigitInUnicodeEscape => r"more than six digits in `\u{...}` escape",
+ InvalidUnicodeEscapeChar => r"value specified in `\u{...}` escape is not a valid char",
+ UnterminatedUnicodeEscape => r"unterminated `\u{...}` escape",
+ UnterminatedCharLiteral => "character literal is not terminated",
+ OverlongCharLiteral => "character literal contains more than one character",
+ EmptyCharLiteral => "empty character literal",
+ UnterminatedByteLiteral => "byte literal is not terminated",
+ OverlongByteLiteral => "byte literal contains more than one byte",
+ EmptyByteLiteral => "empty byte literal",
+ NonAsciiInByteLiteral => "non ASCII character in byte (string) literal",
+ UnescapedSingleQuote => "character literal contains unescaped ' character",
+ UnescapedSpecialWhitespace => r"unescaped newline (\n), tab (\t) or cr (\r) character",
+ DoesNotStartWithQuote => "invalid start for char/byte/string literal",
+ UnterminatedRawString => "unterminated raw (byte) string literal",
+ UnterminatedString => "unterminated (byte) string literal",
+ InvalidStringLiteralStart => "invalid start for string literal",
+ InvalidByteLiteralStart => "invalid start for byte literal",
+ InvalidByteStringLiteralStart => "invalid start for byte string literal",
+ IsolatedCr => r"`\r` not immediately followed by `\n` in string",
+ InvalidSuffix => "literal suffix is not a valid identifier",
+ UnexpectedIntegerLit => "expected float literal, but found integer",
+ IntegerSuffixStartingWithE => "integer literal suffix must not start with 'e' or 'E'",
+ };
+
+ description.fmt(f)?;
+ if let Some(span) = &self.span {
+ write!(f, " (at {}..{})", span.start, span.end)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/third_party/rust/litrs/src/escape.rs b/third_party/rust/litrs/src/escape.rs
new file mode 100644
index 0000000000..5eb8382bc4
--- /dev/null
+++ b/third_party/rust/litrs/src/escape.rs
@@ -0,0 +1,262 @@
+use crate::{ParseError, err::{perr, ParseErrorKind::*}, parse::{hex_digit_value, check_suffix}};
+
+
+/// Must start with `\`
+pub(crate) fn unescape<E: Escapee>(input: &str, offset: usize) -> Result<(E, usize), ParseError> {
+ let first = input.as_bytes().get(1)
+ .ok_or(perr(offset, UnterminatedEscape))?;
+ let out = match first {
+ // Quote escapes
+ b'\'' => (E::from_byte(b'\''), 2),
+ b'"' => (E::from_byte(b'"'), 2),
+
+ // Ascii escapes
+ b'n' => (E::from_byte(b'\n'), 2),
+ b'r' => (E::from_byte(b'\r'), 2),
+ b't' => (E::from_byte(b'\t'), 2),
+ b'\\' => (E::from_byte(b'\\'), 2),
+ b'0' => (E::from_byte(b'\0'), 2),
+ b'x' => {
+ let hex_string = input.get(2..4)
+ .ok_or(perr(offset..offset + input.len(), UnterminatedEscape))?
+ .as_bytes();
+ let first = hex_digit_value(hex_string[0])
+ .ok_or(perr(offset..offset + 4, InvalidXEscape))?;
+ let second = hex_digit_value(hex_string[1])
+ .ok_or(perr(offset..offset + 4, InvalidXEscape))?;
+ let value = second + 16 * first;
+
+ if E::SUPPORTS_UNICODE && value > 0x7F {
+ return Err(perr(offset..offset + 4, NonAsciiXEscape));
+ }
+
+ (E::from_byte(value), 4)
+ },
+
+ // Unicode escape
+ b'u' => {
+ if !E::SUPPORTS_UNICODE {
+ return Err(perr(offset..offset + 2, UnicodeEscapeInByteLiteral));
+ }
+
+ if input.as_bytes().get(2) != Some(&b'{') {
+ return Err(perr(offset..offset + 2, UnicodeEscapeWithoutBrace));
+ }
+
+ let closing_pos = input.bytes().position(|b| b == b'}')
+ .ok_or(perr(offset..offset + input.len(), UnterminatedUnicodeEscape))?;
+
+ let inner = &input[3..closing_pos];
+ if inner.as_bytes().first() == Some(&b'_') {
+ return Err(perr(4, InvalidStartOfUnicodeEscape));
+ }
+
+ let mut v: u32 = 0;
+ let mut digit_count = 0;
+ for (i, b) in inner.bytes().enumerate() {
+ if b == b'_'{
+ continue;
+ }
+
+ let digit = hex_digit_value(b)
+ .ok_or(perr(offset + 3 + i, NonHexDigitInUnicodeEscape))?;
+
+ if digit_count == 6 {
+ return Err(perr(offset + 3 + i, TooManyDigitInUnicodeEscape));
+ }
+ digit_count += 1;
+ v = 16 * v + digit as u32;
+ }
+
+ let c = std::char::from_u32(v)
+ .ok_or(perr(offset..closing_pos + 1, InvalidUnicodeEscapeChar))?;
+
+ (E::from_char(c), closing_pos + 1)
+ }
+
+ _ => return Err(perr(offset..offset + 2, UnknownEscape)),
+ };
+
+ Ok(out)
+}
+
+pub(crate) trait Escapee: Into<char> {
+ const SUPPORTS_UNICODE: bool;
+ fn from_byte(b: u8) -> Self;
+ fn from_char(c: char) -> Self;
+}
+
+impl Escapee for u8 {
+ const SUPPORTS_UNICODE: bool = false;
+ fn from_byte(b: u8) -> Self {
+ b
+ }
+ fn from_char(_: char) -> Self {
+ panic!("bug: `<u8 as Escapee>::from_char` was called");
+ }
+}
+
+impl Escapee for char {
+ const SUPPORTS_UNICODE: bool = true;
+ fn from_byte(b: u8) -> Self {
+ b.into()
+ }
+ fn from_char(c: char) -> Self {
+ c
+ }
+}
+
+/// Checks whether the character is skipped after a string continue start
+/// (unescaped backlash followed by `\n`).
+fn is_string_continue_skipable_whitespace(b: u8) -> bool {
+ b == b' ' || b == b'\t' || b == b'\n' || b == b'\r'
+}
+
+/// Unescapes a whole string or byte string.
+#[inline(never)]
+pub(crate) fn unescape_string<E: Escapee>(
+ input: &str,
+ offset: usize,
+) -> Result<(Option<String>, usize), ParseError> {
+ let mut closing_quote_pos = None;
+ let mut i = offset;
+ let mut end_last_escape = offset;
+ let mut value = String::new();
+ while i < input.len() {
+ match input.as_bytes()[i] {
+ // Handle "string continue".
+ b'\\' if input.as_bytes().get(i + 1) == Some(&b'\n') => {
+ value.push_str(&input[end_last_escape..i]);
+
+ // Find the first non-whitespace character.
+ let end_escape = input[i + 2..].bytes()
+ .position(|b| !is_string_continue_skipable_whitespace(b))
+ .ok_or(perr(None, UnterminatedString))?;
+
+ i += 2 + end_escape;
+ end_last_escape = i;
+ }
+ b'\\' => {
+ let (c, len) = unescape::<E>(&input[i..input.len() - 1], i)?;
+ value.push_str(&input[end_last_escape..i]);
+ value.push(c.into());
+ i += len;
+ end_last_escape = i;
+ }
+ b'\r' => {
+ if input.as_bytes().get(i + 1) == Some(&b'\n') {
+ value.push_str(&input[end_last_escape..i]);
+ value.push('\n');
+ i += 2;
+ end_last_escape = i;
+ } else {
+ return Err(perr(i, IsolatedCr))
+ }
+ }
+ b'"' => {
+ closing_quote_pos = Some(i);
+ break;
+ },
+ b if !E::SUPPORTS_UNICODE && !b.is_ascii()
+ => return Err(perr(i, NonAsciiInByteLiteral)),
+ _ => i += 1,
+ }
+ }
+
+ let closing_quote_pos = closing_quote_pos.ok_or(perr(None, UnterminatedString))?;
+
+ let start_suffix = closing_quote_pos + 1;
+ let suffix = &input[start_suffix..];
+ check_suffix(suffix).map_err(|kind| perr(start_suffix, kind))?;
+
+ // `value` is only empty if there was no escape in the input string
+ // (with the special case of the input being empty). This means the
+ // string value basically equals the input, so we store `None`.
+ let value = if value.is_empty() {
+ None
+ } else {
+ // There was an escape in the string, so we need to push the
+ // remaining unescaped part of the string still.
+ value.push_str(&input[end_last_escape..closing_quote_pos]);
+ Some(value)
+ };
+
+ Ok((value, start_suffix))
+}
+
+/// Reads and checks a raw (byte) string literal, converting `\r\n` sequences to
+/// just `\n` sequences. Returns an optional new string (if the input contained
+/// any `\r\n`) and the number of hashes used by the literal.
+#[inline(never)]
+pub(crate) fn scan_raw_string<E: Escapee>(
+ input: &str,
+ offset: usize,
+) -> Result<(Option<String>, u32, usize), ParseError> {
+ // Raw string literal
+ let num_hashes = input[offset..].bytes().position(|b| b != b'#')
+ .ok_or(perr(None, InvalidLiteral))?;
+
+ if input.as_bytes().get(offset + num_hashes) != Some(&b'"') {
+ return Err(perr(None, InvalidLiteral));
+ }
+ let start_inner = offset + num_hashes + 1;
+ let hashes = &input[offset..num_hashes + offset];
+
+ let mut closing_quote_pos = None;
+ let mut i = start_inner;
+ let mut end_last_escape = start_inner;
+ let mut value = String::new();
+ while i < input.len() {
+ let b = input.as_bytes()[i];
+ if b == b'"' && input[i + 1..].starts_with(hashes) {
+ closing_quote_pos = Some(i);
+ break;
+ }
+
+ if b == b'\r' {
+ // Convert `\r\n` into `\n`. This is currently not well documented
+ // in the Rust reference, but is done even for raw strings. That's
+ // because rustc simply converts all line endings when reading
+ // source files.
+ if input.as_bytes().get(i + 1) == Some(&b'\n') {
+ value.push_str(&input[end_last_escape..i]);
+ value.push('\n');
+ i += 2;
+ end_last_escape = i;
+ continue;
+ } else if E::SUPPORTS_UNICODE {
+ // If no \n follows the \r and we are scanning a raw string
+ // (not raw byte string), we error.
+ return Err(perr(i, IsolatedCr))
+ }
+ }
+
+ if !E::SUPPORTS_UNICODE {
+ if !b.is_ascii() {
+ return Err(perr(i, NonAsciiInByteLiteral));
+ }
+ }
+
+ i += 1;
+ }
+
+ let closing_quote_pos = closing_quote_pos.ok_or(perr(None, UnterminatedRawString))?;
+
+ let start_suffix = closing_quote_pos + num_hashes + 1;
+ let suffix = &input[start_suffix..];
+ check_suffix(suffix).map_err(|kind| perr(start_suffix, kind))?;
+
+ // `value` is only empty if there was no \r\n in the input string (with the
+ // special case of the input being empty). This means the string value
+ // equals the input, so we store `None`.
+ let value = if value.is_empty() {
+ None
+ } else {
+ // There was an \r\n in the string, so we need to push the remaining
+ // unescaped part of the string still.
+ value.push_str(&input[end_last_escape..closing_quote_pos]);
+ Some(value)
+ };
+
+ Ok((value, num_hashes as u32, start_suffix))
+}
diff --git a/third_party/rust/litrs/src/float/mod.rs b/third_party/rust/litrs/src/float/mod.rs
new file mode 100644
index 0000000000..0518633a6b
--- /dev/null
+++ b/third_party/rust/litrs/src/float/mod.rs
@@ -0,0 +1,257 @@
+use std::{fmt, str::FromStr};
+
+use crate::{
+ Buffer, ParseError,
+ err::{perr, ParseErrorKind::*},
+ parse::{end_dec_digits, first_byte_or_empty, check_suffix},
+};
+
+
+
+/// A floating point literal, e.g. `3.14`, `8.`, `135e12`, or `1.956e2f64`.
+///
+/// This kind of literal has several forms, but generally consists of a main
+/// number part, an optional exponent and an optional type suffix. See
+/// [the reference][ref] for more information.
+///
+/// A leading minus sign `-` is not part of the literal grammar! `-3.14` are two
+/// tokens in the Rust grammar. Further, `27` and `27f32` are both not float,
+/// but integer literals! Consequently `FloatLit::parse` will reject them.
+///
+///
+/// [ref]: https://doc.rust-lang.org/reference/tokens.html#floating-point-literals
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct FloatLit<B: Buffer> {
+ /// The whole raw input. The `usize` fields in this struct partition this
+ /// string. Always true: `end_integer_part <= end_fractional_part`.
+ ///
+ /// ```text
+ /// 12_3.4_56e789f32
+ /// ╷ ╷ ╷
+ /// | | └ end_number_part = 13
+ /// | └ end_fractional_part = 9
+ /// └ end_integer_part = 4
+ ///
+ /// 246.
+ /// ╷╷
+ /// |└ end_fractional_part = end_number_part = 4
+ /// └ end_integer_part = 3
+ ///
+ /// 1234e89
+ /// ╷ ╷
+ /// | └ end_number_part = 7
+ /// └ end_integer_part = end_fractional_part = 4
+ /// ```
+ raw: B,
+
+ /// The first index not part of the integer part anymore. Since the integer
+ /// part is at the start, this is also the length of that part.
+ end_integer_part: usize,
+
+ /// The first index after the fractional part.
+ end_fractional_part: usize,
+
+ /// The first index after the whole number part (everything except type suffix).
+ end_number_part: usize,
+}
+
+impl<B: Buffer> FloatLit<B> {
+ /// Parses the input as a floating point literal. Returns an error if the
+ /// input is invalid or represents a different kind of literal. Will also
+ /// reject decimal integer literals like `23` or `17f32`, in accordance
+ /// with the spec.
+ pub fn parse(s: B) -> Result<Self, ParseError> {
+ match first_byte_or_empty(&s)? {
+ b'0'..=b'9' => {
+ // TODO: simplify once RFC 2528 is stabilized
+ let FloatLit {
+ end_integer_part,
+ end_fractional_part,
+ end_number_part,
+ ..
+ } = parse_impl(&s)?;
+
+ Ok(Self { raw: s, end_integer_part, end_fractional_part, end_number_part })
+ },
+ _ => Err(perr(0, DoesNotStartWithDigit)),
+ }
+ }
+
+ /// Returns the number part (including integer part, fractional part and
+ /// exponent), but without the suffix. If you want an actual floating
+ /// point value, you need to parse this string, e.g. with `f32::from_str`
+ /// or an external crate.
+ pub fn number_part(&self) -> &str {
+ &(*self.raw)[..self.end_number_part]
+ }
+
+ /// Returns the non-empty integer part of this literal.
+ pub fn integer_part(&self) -> &str {
+ &(*self.raw)[..self.end_integer_part]
+ }
+
+ /// Returns the optional fractional part of this literal. Does not include
+ /// the period. If a period exists in the input, `Some` is returned, `None`
+ /// otherwise. Note that `Some("")` might be returned, e.g. for `3.`.
+ pub fn fractional_part(&self) -> Option<&str> {
+ if self.end_integer_part == self.end_fractional_part {
+ None
+ } else {
+ Some(&(*self.raw)[self.end_integer_part + 1..self.end_fractional_part])
+ }
+ }
+
+ /// Optional exponent part. Might be empty if there was no exponent part in
+ /// the input. Includes the `e` or `E` at the beginning.
+ pub fn exponent_part(&self) -> &str {
+ &(*self.raw)[self.end_fractional_part..self.end_number_part]
+ }
+
+ /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
+ pub fn suffix(&self) -> &str {
+ &(*self.raw)[self.end_number_part..]
+ }
+
+ /// Returns the raw input that was passed to `parse`.
+ pub fn raw_input(&self) -> &str {
+ &self.raw
+ }
+
+ /// Returns the raw input that was passed to `parse`, potentially owned.
+ pub fn into_raw_input(self) -> B {
+ self.raw
+ }
+}
+
+impl FloatLit<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn to_owned(&self) -> FloatLit<String> {
+ FloatLit {
+ raw: self.raw.to_owned(),
+ end_integer_part: self.end_integer_part,
+ end_fractional_part: self.end_fractional_part,
+ end_number_part: self.end_number_part,
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for FloatLit<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", &*self.raw)
+ }
+}
+
+/// Precondition: first byte of string has to be in `b'0'..=b'9'`.
+#[inline(never)]
+pub(crate) fn parse_impl(input: &str) -> Result<FloatLit<&str>, ParseError> {
+ // Integer part.
+ let end_integer_part = end_dec_digits(input.as_bytes());
+ let rest = &input[end_integer_part..];
+
+
+ // Fractional part.
+ let end_fractional_part = if rest.as_bytes().get(0) == Some(&b'.') {
+ // The fractional part must not start with `_`.
+ if rest.as_bytes().get(1) == Some(&b'_') {
+ return Err(perr(end_integer_part + 1, UnexpectedChar));
+ }
+
+ end_dec_digits(rest[1..].as_bytes()) + 1 + end_integer_part
+ } else {
+ end_integer_part
+ };
+ let rest = &input[end_fractional_part..];
+
+ // If we have a period that is not followed by decimal digits, the
+ // literal must end now.
+ if end_integer_part + 1 == end_fractional_part && !rest.is_empty() {
+ return Err(perr(end_integer_part + 1, UnexpectedChar));
+ }
+
+ // Optional exponent.
+ let end_number_part = if rest.starts_with('e') || rest.starts_with('E') {
+ // Strip single - or + sign at the beginning.
+ let exp_number_start = match rest.as_bytes().get(1) {
+ Some(b'-') | Some(b'+') => 2,
+ _ => 1,
+ };
+
+ // Find end of exponent and make sure there is at least one digit.
+ let end_exponent = end_dec_digits(rest[exp_number_start..].as_bytes()) + exp_number_start;
+ if !rest[exp_number_start..end_exponent].bytes().any(|b| matches!(b, b'0'..=b'9')) {
+ return Err(perr(
+ end_fractional_part..end_fractional_part + end_exponent,
+ NoExponentDigits,
+ ));
+ }
+
+ end_exponent + end_fractional_part
+ } else {
+ end_fractional_part
+ };
+
+ // Make sure the suffix is valid.
+ let suffix = &input[end_number_part..];
+ check_suffix(suffix).map_err(|kind| perr(end_number_part..input.len(), kind))?;
+
+ // A float literal needs either a fractional or exponent part, otherwise its
+ // an integer literal.
+ if end_integer_part == end_number_part {
+ return Err(perr(None, UnexpectedIntegerLit));
+ }
+
+ Ok(FloatLit {
+ raw: input,
+ end_integer_part,
+ end_fractional_part,
+ end_number_part,
+ })
+}
+
+
+/// All possible float type suffixes.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum FloatType {
+ F32,
+ F64,
+}
+
+impl FloatType {
+ /// Returns the type corresponding to the given suffix (e.g. `"f32"` is
+ /// mapped to `Self::F32`). If the suffix is not a valid float type, `None`
+ /// is returned.
+ pub fn from_suffix(suffix: &str) -> Option<Self> {
+ match suffix {
+ "f32" => Some(FloatType::F32),
+ "f64" => Some(FloatType::F64),
+ _ => None,
+ }
+ }
+
+ /// Returns the suffix for this type, e.g. `"f32"` for `Self::F32`.
+ pub fn suffix(self) -> &'static str {
+ match self {
+ Self::F32 => "f32",
+ Self::F64 => "f64",
+ }
+ }
+}
+
+impl FromStr for FloatType {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Self::from_suffix(s).ok_or(())
+ }
+}
+
+impl fmt::Display for FloatType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.suffix().fmt(f)
+ }
+}
+
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/float/tests.rs b/third_party/rust/litrs/src/float/tests.rs
new file mode 100644
index 0000000000..f22443bd19
--- /dev/null
+++ b/third_party/rust/litrs/src/float/tests.rs
@@ -0,0 +1,253 @@
+use crate::{
+ Literal, ParseError,
+ test_util::{assert_parse_ok_eq, assert_roundtrip},
+};
+use super::{FloatLit, FloatType};
+
+
+// ===== Utility functions =======================================================================
+
+/// Helper macro to check parsing a float.
+///
+/// This macro contains quite a bit of logic itself (which can be buggy of
+/// course), so we have a few test functions below to test a bunch of cases
+/// manually.
+macro_rules! check {
+ ($intpart:literal $fracpart:literal $exppart:literal $suffix:tt) => {
+ let input = concat!($intpart, $fracpart, $exppart, check!(@stringify_suffix $suffix));
+ let expected_float = FloatLit {
+ raw: input,
+ end_integer_part: $intpart.len(),
+ end_fractional_part: $intpart.len() + $fracpart.len(),
+ end_number_part: $intpart.len() + $fracpart.len() + $exppart.len(),
+ };
+
+ assert_parse_ok_eq(
+ input, FloatLit::parse(input), expected_float.clone(), "FloatLit::parse");
+ assert_parse_ok_eq(
+ input, Literal::parse(input), Literal::Float(expected_float), "Literal::parse");
+ assert_eq!(FloatLit::parse(input).unwrap().suffix(), check!(@ty $suffix));
+ assert_roundtrip(expected_float.to_owned(), input);
+ };
+ (@ty f32) => { "f32" };
+ (@ty f64) => { "f64" };
+ (@ty -) => { "" };
+ (@stringify_suffix -) => { "" };
+ (@stringify_suffix $suffix:ident) => { stringify!($suffix) };
+}
+
+
+// ===== Actual tests ===========================================================================
+
+#[test]
+fn manual_without_suffix() -> Result<(), ParseError> {
+ let f = FloatLit::parse("3.14")?;
+ assert_eq!(f.number_part(), "3.14");
+ assert_eq!(f.integer_part(), "3");
+ assert_eq!(f.fractional_part(), Some("14"));
+ assert_eq!(f.exponent_part(), "");
+ assert_eq!(f.suffix(), "");
+
+ let f = FloatLit::parse("9.")?;
+ assert_eq!(f.number_part(), "9.");
+ assert_eq!(f.integer_part(), "9");
+ assert_eq!(f.fractional_part(), Some(""));
+ assert_eq!(f.exponent_part(), "");
+ assert_eq!(f.suffix(), "");
+
+ let f = FloatLit::parse("8e1")?;
+ assert_eq!(f.number_part(), "8e1");
+ assert_eq!(f.integer_part(), "8");
+ assert_eq!(f.fractional_part(), None);
+ assert_eq!(f.exponent_part(), "e1");
+ assert_eq!(f.suffix(), "");
+
+ let f = FloatLit::parse("8E3")?;
+ assert_eq!(f.number_part(), "8E3");
+ assert_eq!(f.integer_part(), "8");
+ assert_eq!(f.fractional_part(), None);
+ assert_eq!(f.exponent_part(), "E3");
+ assert_eq!(f.suffix(), "");
+
+ let f = FloatLit::parse("8_7_6.1_23e15")?;
+ assert_eq!(f.number_part(), "8_7_6.1_23e15");
+ assert_eq!(f.integer_part(), "8_7_6");
+ assert_eq!(f.fractional_part(), Some("1_23"));
+ assert_eq!(f.exponent_part(), "e15");
+ assert_eq!(f.suffix(), "");
+
+ let f = FloatLit::parse("8.2e-_04_9")?;
+ assert_eq!(f.number_part(), "8.2e-_04_9");
+ assert_eq!(f.integer_part(), "8");
+ assert_eq!(f.fractional_part(), Some("2"));
+ assert_eq!(f.exponent_part(), "e-_04_9");
+ assert_eq!(f.suffix(), "");
+
+ Ok(())
+}
+
+#[test]
+fn manual_with_suffix() -> Result<(), ParseError> {
+ let f = FloatLit::parse("3.14f32")?;
+ assert_eq!(f.number_part(), "3.14");
+ assert_eq!(f.integer_part(), "3");
+ assert_eq!(f.fractional_part(), Some("14"));
+ assert_eq!(f.exponent_part(), "");
+ assert_eq!(FloatType::from_suffix(f.suffix()), Some(FloatType::F32));
+
+ let f = FloatLit::parse("8e1f64")?;
+ assert_eq!(f.number_part(), "8e1");
+ assert_eq!(f.integer_part(), "8");
+ assert_eq!(f.fractional_part(), None);
+ assert_eq!(f.exponent_part(), "e1");
+ assert_eq!(FloatType::from_suffix(f.suffix()), Some(FloatType::F64));
+
+ let f = FloatLit::parse("8_7_6.1_23e15f32")?;
+ assert_eq!(f.number_part(), "8_7_6.1_23e15");
+ assert_eq!(f.integer_part(), "8_7_6");
+ assert_eq!(f.fractional_part(), Some("1_23"));
+ assert_eq!(f.exponent_part(), "e15");
+ assert_eq!(FloatType::from_suffix(f.suffix()), Some(FloatType::F32));
+
+ let f = FloatLit::parse("8.2e-_04_9f64")?;
+ assert_eq!(f.number_part(), "8.2e-_04_9");
+ assert_eq!(f.integer_part(), "8");
+ assert_eq!(f.fractional_part(), Some("2"));
+ assert_eq!(f.exponent_part(), "e-_04_9");
+ assert_eq!(FloatType::from_suffix(f.suffix()), Some(FloatType::F64));
+
+ Ok(())
+}
+
+#[test]
+fn simple() {
+ check!("3" ".14" "" -);
+ check!("3" ".14" "" f32);
+ check!("3" ".14" "" f64);
+
+ check!("3" "" "e987654321" -);
+ check!("3" "" "e987654321" f64);
+
+ check!("42_888" ".05" "" -);
+ check!("42_888" ".05" "E5___" f32);
+ check!("123456789" "" "e_1" f64);
+ check!("123456789" ".99" "e_1" f64);
+ check!("123456789" ".99" "" f64);
+ check!("123456789" ".99" "" -);
+
+ check!("147" ".3_33" "" -);
+ check!("147" ".3_33__" "E3" f64);
+ check!("147" ".3_33__" "" f32);
+
+ check!("147" ".333" "e-10" -);
+ check!("147" ".333" "e-_7" f32);
+ check!("147" ".333" "e+10" -);
+ check!("147" ".333" "e+_7" f32);
+
+ check!("86" "." "" -);
+ check!("0" "." "" -);
+ check!("0_" "." "" -);
+ check!("0" ".0000001" "" -);
+ check!("0" ".000_0001" "" -);
+
+ check!("0" ".0" "e+0" -);
+ check!("0" "" "E+0" -);
+ check!("34" "" "e+0" -);
+ check!("0" ".9182" "E+0" f32);
+}
+
+#[test]
+fn non_standard_suffixes() {
+ #[track_caller]
+ fn check_suffix(
+ input: &str,
+ integer_part: &str,
+ fractional_part: Option<&str>,
+ exponent_part: &str,
+ suffix: &str,
+ ) {
+ let lit = FloatLit::parse(input)
+ .unwrap_or_else(|e| panic!("expected to parse '{}' but got {}", input, e));
+ assert_eq!(lit.integer_part(), integer_part);
+ assert_eq!(lit.fractional_part(), fractional_part);
+ assert_eq!(lit.exponent_part(), exponent_part);
+ assert_eq!(lit.suffix(), suffix);
+
+ let lit = match Literal::parse(input) {
+ Ok(Literal::Float(f)) => f,
+ other => panic!("Expected float literal, but got {:?} for '{}'", other, input),
+ };
+ assert_eq!(lit.integer_part(), integer_part);
+ assert_eq!(lit.fractional_part(), fractional_part);
+ assert_eq!(lit.exponent_part(), exponent_part);
+ assert_eq!(lit.suffix(), suffix);
+ }
+
+ check_suffix("7.1f23", "7", Some("1"), "", "f23");
+ check_suffix("7.1f320", "7", Some("1"), "", "f320");
+ check_suffix("7.1f64_", "7", Some("1"), "", "f64_");
+ check_suffix("8.1f649", "8", Some("1"), "", "f649");
+ check_suffix("8.1f64f32", "8", Some("1"), "", "f64f32");
+ check_suffix("23e2_banana", "23", None, "e2_", "banana");
+ check_suffix("23.2_banana", "23", Some("2_"), "", "banana");
+ check_suffix("23e2pe55ter", "23", None, "e2", "pe55ter");
+ check_suffix("23e2p_e55ter", "23", None, "e2", "p_e55ter");
+ check_suffix("3.15Jürgen", "3", Some("15"), "", "Jürgen");
+ check_suffix("3e2e5", "3", None, "e2", "e5");
+ check_suffix("3e2e5f", "3", None, "e2", "e5f");
+}
+
+#[test]
+fn parse_err() {
+ assert_err!(FloatLit, "", Empty, None);
+ assert_err_single!(FloatLit::parse("."), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("+"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("-"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("e"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("e8"), DoesNotStartWithDigit, 0);
+ assert_err!(FloatLit, "0e", NoExponentDigits, 1..2);
+ assert_err_single!(FloatLit::parse("f32"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("foo"), DoesNotStartWithDigit, 0);
+
+ assert_err_single!(FloatLit::parse("inf"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("nan"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("NaN"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse("NAN"), DoesNotStartWithDigit, 0);
+
+ assert_err_single!(FloatLit::parse("_2.7"), DoesNotStartWithDigit, 0);
+ assert_err_single!(FloatLit::parse(".5"), DoesNotStartWithDigit, 0);
+ assert_err!(FloatLit, "1e", NoExponentDigits, 1..2);
+ assert_err!(FloatLit, "1.e4", UnexpectedChar, 2);
+ assert_err!(FloatLit, "3._4", UnexpectedChar, 2);
+ assert_err!(FloatLit, "3.f32", UnexpectedChar, 2);
+ assert_err!(FloatLit, "3.e5", UnexpectedChar, 2);
+ assert_err!(FloatLit, "12345._987", UnexpectedChar, 6);
+ assert_err!(FloatLit, "46._", UnexpectedChar, 3);
+ assert_err!(FloatLit, "46.f32", UnexpectedChar, 3);
+ assert_err!(FloatLit, "46.e3", UnexpectedChar, 3);
+ assert_err!(FloatLit, "46._e3", UnexpectedChar, 3);
+ assert_err!(FloatLit, "46.e3f64", UnexpectedChar, 3);
+ assert_err!(FloatLit, "23.4e_", NoExponentDigits, 4..6);
+ assert_err!(FloatLit, "23E___f32", NoExponentDigits, 2..6);
+ assert_err!(FloatLit, "55e3.1", UnexpectedChar, 4..6);
+
+ assert_err!(FloatLit, "3.7+", UnexpectedChar, 3..4);
+ assert_err!(FloatLit, "3.7+2", UnexpectedChar, 3..5);
+ assert_err!(FloatLit, "3.7-", UnexpectedChar, 3..4);
+ assert_err!(FloatLit, "3.7-2", UnexpectedChar, 3..5);
+ assert_err!(FloatLit, "3.7e+", NoExponentDigits, 3..5);
+ assert_err!(FloatLit, "3.7e-", NoExponentDigits, 3..5);
+ assert_err!(FloatLit, "3.7e-+3", NoExponentDigits, 3..5); // suboptimal error
+ assert_err!(FloatLit, "3.7e+-3", NoExponentDigits, 3..5); // suboptimal error
+ assert_err_single!(FloatLit::parse("0x44.5"), InvalidSuffix, 1..6);
+
+ assert_err_single!(FloatLit::parse("3"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("35_389"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("9_8_7f32"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("9_8_7banana"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("7f23"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("7f320"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("7f64_"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("8f649"), UnexpectedIntegerLit, None);
+ assert_err_single!(FloatLit::parse("8f64f32"), UnexpectedIntegerLit, None);
+}
diff --git a/third_party/rust/litrs/src/impls.rs b/third_party/rust/litrs/src/impls.rs
new file mode 100644
index 0000000000..61a314dd84
--- /dev/null
+++ b/third_party/rust/litrs/src/impls.rs
@@ -0,0 +1,401 @@
+use std::convert::TryFrom;
+
+use crate::{Literal, err::{InvalidToken, TokenKind}};
+
+
+/// Helper macro to call a `callback` macro four times for all combinations of
+/// `proc_macro`/`proc_macro2` and `&`/owned.
+macro_rules! helper {
+ ($callback:ident, $($input:tt)*) => {
+ $callback!([proc_macro::] => $($input)*);
+ $callback!([&proc_macro::] => $($input)*);
+ #[cfg(feature = "proc-macro2")]
+ $callback!([proc_macro2::] => $($input)*);
+ #[cfg(feature = "proc-macro2")]
+ $callback!([&proc_macro2::] => $($input)*);
+ };
+}
+
+/// Like `helper!` but without reference types.
+macro_rules! helper_no_refs {
+ ($callback:ident, $($input:tt)*) => {
+ $callback!([proc_macro::] => $($input)*);
+ #[cfg(feature = "proc-macro2")]
+ $callback!([proc_macro2::] => $($input)*);
+ };
+}
+
+
+// ==============================================================================================
+// ===== `From<*Lit> for Literal`
+// ==============================================================================================
+
+macro_rules! impl_specific_lit_to_lit {
+ ($ty:ty, $variant:ident) => {
+ impl<B: crate::Buffer> From<$ty> for Literal<B> {
+ fn from(src: $ty) -> Self {
+ Literal::$variant(src)
+ }
+ }
+ };
+}
+
+impl_specific_lit_to_lit!(crate::BoolLit, Bool);
+impl_specific_lit_to_lit!(crate::IntegerLit<B>, Integer);
+impl_specific_lit_to_lit!(crate::FloatLit<B>, Float);
+impl_specific_lit_to_lit!(crate::CharLit<B>, Char);
+impl_specific_lit_to_lit!(crate::StringLit<B>, String);
+impl_specific_lit_to_lit!(crate::ByteLit<B>, Byte);
+impl_specific_lit_to_lit!(crate::ByteStringLit<B>, ByteString);
+
+
+
+// ==============================================================================================
+// ===== `From<pm::Literal> for Literal`
+// ==============================================================================================
+
+
+macro_rules! impl_tt_to_lit {
+ ([$($prefix:tt)*] => ) => {
+ impl From<$($prefix)* Literal> for Literal<String> {
+ fn from(src: $($prefix)* Literal) -> Self {
+ // We call `expect` in all these impls: this library aims to implement exactly
+ // the Rust grammar, so if we have a valid Rust literal, we should always be
+ // able to parse it.
+ Self::parse(src.to_string())
+ .expect("bug: failed to parse output of `Literal::to_string`")
+ }
+ }
+ }
+}
+
+helper!(impl_tt_to_lit, );
+
+
+// ==============================================================================================
+// ===== `TryFrom<pm::TokenTree> for Literal`
+// ==============================================================================================
+
+macro_rules! impl_tt_to_lit {
+ ([$($prefix:tt)*] => ) => {
+ impl TryFrom<$($prefix)* TokenTree> for Literal<String> {
+ type Error = InvalidToken;
+ fn try_from(tt: $($prefix)* TokenTree) -> Result<Self, Self::Error> {
+ let span = tt.span();
+ let res = match tt {
+ $($prefix)* TokenTree::Group(_) => Err(TokenKind::Group),
+ $($prefix)* TokenTree::Punct(_) => Err(TokenKind::Punct),
+ $($prefix)* TokenTree::Ident(ref ident) if ident.to_string() == "true"
+ => return Ok(Literal::Bool(crate::BoolLit::True)),
+ $($prefix)* TokenTree::Ident(ref ident) if ident.to_string() == "false"
+ => return Ok(Literal::Bool(crate::BoolLit::False)),
+ $($prefix)* TokenTree::Ident(_) => Err(TokenKind::Ident),
+ $($prefix)* TokenTree::Literal(ref lit) => Ok(lit),
+ };
+
+ match res {
+ Ok(lit) => Ok(From::from(lit)),
+ Err(actual) => Err(InvalidToken {
+ actual,
+ expected: TokenKind::Literal,
+ span: span.into(),
+ }),
+ }
+ }
+ }
+ }
+}
+
+helper!(impl_tt_to_lit, );
+
+
+// ==============================================================================================
+// ===== `TryFrom<pm::Literal>`, `TryFrom<pm::TokenTree>` for non-bool `*Lit`
+// ==============================================================================================
+
+fn kind_of(lit: &Literal<String>) -> TokenKind {
+ match lit {
+ Literal::String(_) => TokenKind::StringLit,
+ Literal::Bool(_) => TokenKind::BoolLit,
+ Literal::Integer(_) => TokenKind::IntegerLit,
+ Literal::Float(_) => TokenKind::FloatLit,
+ Literal::Char(_) => TokenKind::CharLit,
+ Literal::Byte(_) => TokenKind::ByteLit,
+ Literal::ByteString(_) => TokenKind::ByteStringLit,
+ }
+}
+
+macro_rules! impl_for_specific_lit {
+ ([$($prefix:tt)*] => $ty:ty, $variant:ident, $kind:ident) => {
+ impl TryFrom<$($prefix)* Literal> for $ty {
+ type Error = InvalidToken;
+ fn try_from(src: $($prefix)* Literal) -> Result<Self, Self::Error> {
+ let span = src.span();
+ let lit: Literal<String> = src.into();
+ match lit {
+ Literal::$variant(s) => Ok(s),
+ other => Err(InvalidToken {
+ expected: TokenKind::$kind,
+ actual: kind_of(&other),
+ span: span.into(),
+ }),
+ }
+ }
+ }
+
+ impl TryFrom<$($prefix)* TokenTree> for $ty {
+ type Error = InvalidToken;
+ fn try_from(tt: $($prefix)* TokenTree) -> Result<Self, Self::Error> {
+ let span = tt.span();
+ let res = match tt {
+ $($prefix)* TokenTree::Group(_) => Err(TokenKind::Group),
+ $($prefix)* TokenTree::Punct(_) => Err(TokenKind::Punct),
+ $($prefix)* TokenTree::Ident(_) => Err(TokenKind::Ident),
+ $($prefix)* TokenTree::Literal(ref lit) => Ok(lit),
+ };
+
+ match res {
+ Ok(lit) => <$ty>::try_from(lit),
+ Err(actual) => Err(InvalidToken {
+ actual,
+ expected: TokenKind::$kind,
+ span: span.into(),
+ }),
+ }
+ }
+ }
+ };
+}
+
+helper!(impl_for_specific_lit, crate::IntegerLit<String>, Integer, IntegerLit);
+helper!(impl_for_specific_lit, crate::FloatLit<String>, Float, FloatLit);
+helper!(impl_for_specific_lit, crate::CharLit<String>, Char, CharLit);
+helper!(impl_for_specific_lit, crate::StringLit<String>, String, StringLit);
+helper!(impl_for_specific_lit, crate::ByteLit<String>, Byte, ByteLit);
+helper!(impl_for_specific_lit, crate::ByteStringLit<String>, ByteString, ByteStringLit);
+
+
+// ==============================================================================================
+// ===== `From<*Lit> for pm::Literal`
+// ==============================================================================================
+
+macro_rules! impl_specific_lit_to_pm_lit {
+ ([$($prefix:tt)*] => $ty:ident, $variant:ident, $kind:ident) => {
+ impl<B: crate::Buffer> From<crate::$ty<B>> for $($prefix)* Literal {
+ fn from(l: crate::$ty<B>) -> Self {
+ // This should never fail: an input that is parsed successfuly
+ // as one of our literal types should always parse as a
+ // proc_macro literal as well!
+ l.raw_input().parse().unwrap_or_else(|e| {
+ panic!(
+ "failed to parse `{}` as `{}`: {}",
+ l.raw_input(),
+ std::any::type_name::<Self>(),
+ e,
+ )
+ })
+ }
+ }
+ };
+}
+
+helper_no_refs!(impl_specific_lit_to_pm_lit, IntegerLit, Integer, IntegerLit);
+helper_no_refs!(impl_specific_lit_to_pm_lit, FloatLit, Float, FloatLit);
+helper_no_refs!(impl_specific_lit_to_pm_lit, CharLit, Char, CharLit);
+helper_no_refs!(impl_specific_lit_to_pm_lit, StringLit, String, StringLit);
+helper_no_refs!(impl_specific_lit_to_pm_lit, ByteLit, Byte, ByteLit);
+helper_no_refs!(impl_specific_lit_to_pm_lit, ByteStringLit, ByteString, ByteStringLit);
+
+
+// ==============================================================================================
+// ===== `TryFrom<pm::TokenTree> for BoolLit`
+// ==============================================================================================
+
+macro_rules! impl_from_tt_for_bool {
+ ([$($prefix:tt)*] => ) => {
+ impl TryFrom<$($prefix)* TokenTree> for crate::BoolLit {
+ type Error = InvalidToken;
+ fn try_from(tt: $($prefix)* TokenTree) -> Result<Self, Self::Error> {
+ let span = tt.span();
+ let actual = match tt {
+ $($prefix)* TokenTree::Ident(ref ident) if ident.to_string() == "true"
+ => return Ok(crate::BoolLit::True),
+ $($prefix)* TokenTree::Ident(ref ident) if ident.to_string() == "false"
+ => return Ok(crate::BoolLit::False),
+
+ $($prefix)* TokenTree::Group(_) => TokenKind::Group,
+ $($prefix)* TokenTree::Punct(_) => TokenKind::Punct,
+ $($prefix)* TokenTree::Ident(_) => TokenKind::Ident,
+ $($prefix)* TokenTree::Literal(ref lit) => kind_of(&Literal::from(lit)),
+ };
+
+ Err(InvalidToken {
+ actual,
+ expected: TokenKind::BoolLit,
+ span: span.into(),
+ })
+ }
+ }
+ };
+}
+
+helper!(impl_from_tt_for_bool, );
+
+// ==============================================================================================
+// ===== `From<BoolLit> for pm::Ident`
+// ==============================================================================================
+
+macro_rules! impl_bool_lit_to_pm_lit {
+ ([$($prefix:tt)*] => ) => {
+ impl From<crate::BoolLit> for $($prefix)* Ident {
+ fn from(l: crate::BoolLit) -> Self {
+ Self::new(l.as_str(), $($prefix)* Span::call_site())
+ }
+ }
+ };
+}
+
+helper_no_refs!(impl_bool_lit_to_pm_lit, );
+
+
+mod tests {
+ //! # Tests
+ //!
+ //! ```no_run
+ //! extern crate proc_macro;
+ //!
+ //! use std::convert::TryFrom;
+ //! use litrs::Literal;
+ //!
+ //! fn give<T>() -> T {
+ //! panic!()
+ //! }
+ //!
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::BoolLit>());
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::IntegerLit<String>>());
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::FloatLit<String>>());
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::CharLit<String>>());
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::StringLit<String>>());
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::ByteLit<String>>());
+ //! let _ = litrs::Literal::<String>::from(give::<litrs::ByteStringLit<String>>());
+ //!
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::BoolLit>());
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::IntegerLit<&'static str>>());
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::FloatLit<&'static str>>());
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::CharLit<&'static str>>());
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::StringLit<&'static str>>());
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::ByteLit<&'static str>>());
+ //! let _ = litrs::Literal::<&'static str>::from(give::<litrs::ByteStringLit<&'static str>>());
+ //!
+ //!
+ //! let _ = litrs::Literal::from(give::<proc_macro::Literal>());
+ //! let _ = litrs::Literal::from(give::<&proc_macro::Literal>());
+ //!
+ //! let _ = litrs::Literal::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::Literal::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //!
+ //! let _ = litrs::IntegerLit::try_from(give::<proc_macro::Literal>());
+ //! let _ = litrs::IntegerLit::try_from(give::<&proc_macro::Literal>());
+ //!
+ //! let _ = litrs::FloatLit::try_from(give::<proc_macro::Literal>());
+ //! let _ = litrs::FloatLit::try_from(give::<&proc_macro::Literal>());
+ //!
+ //! let _ = litrs::CharLit::try_from(give::<proc_macro::Literal>());
+ //! let _ = litrs::CharLit::try_from(give::<&proc_macro::Literal>());
+ //!
+ //! let _ = litrs::StringLit::try_from(give::<proc_macro::Literal>());
+ //! let _ = litrs::StringLit::try_from(give::<&proc_macro::Literal>());
+ //!
+ //! let _ = litrs::ByteLit::try_from(give::<proc_macro::Literal>());
+ //! let _ = litrs::ByteLit::try_from(give::<&proc_macro::Literal>());
+ //!
+ //! let _ = litrs::ByteStringLit::try_from(give::<proc_macro::Literal>());
+ //! let _ = litrs::ByteStringLit::try_from(give::<&proc_macro::Literal>());
+ //!
+ //!
+ //! let _ = litrs::BoolLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::BoolLit::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //! let _ = litrs::IntegerLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::IntegerLit::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //! let _ = litrs::FloatLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::FloatLit::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //! let _ = litrs::CharLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::CharLit::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //! let _ = litrs::StringLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::StringLit::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //! let _ = litrs::ByteLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::ByteLit::try_from(give::<&proc_macro::TokenTree>());
+ //!
+ //! let _ = litrs::ByteStringLit::try_from(give::<proc_macro::TokenTree>());
+ //! let _ = litrs::ByteStringLit::try_from(give::<&proc_macro::TokenTree>());
+ //! ```
+}
+
+#[cfg(feature = "proc-macro2")]
+mod tests_proc_macro2 {
+ //! # Tests
+ //!
+ //! ```no_run
+ //! extern crate proc_macro;
+ //!
+ //! use std::convert::TryFrom;
+ //! use litrs::Literal;
+ //!
+ //! fn give<T>() -> T {
+ //! panic!()
+ //! }
+ //!
+ //! let _ = litrs::Literal::from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::Literal::from(give::<&proc_macro2::Literal>());
+ //!
+ //! let _ = litrs::Literal::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::Literal::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //!
+ //! let _ = litrs::IntegerLit::try_from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::IntegerLit::try_from(give::<&proc_macro2::Literal>());
+ //!
+ //! let _ = litrs::FloatLit::try_from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::FloatLit::try_from(give::<&proc_macro2::Literal>());
+ //!
+ //! let _ = litrs::CharLit::try_from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::CharLit::try_from(give::<&proc_macro2::Literal>());
+ //!
+ //! let _ = litrs::StringLit::try_from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::StringLit::try_from(give::<&proc_macro2::Literal>());
+ //!
+ //! let _ = litrs::ByteLit::try_from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::ByteLit::try_from(give::<&proc_macro2::Literal>());
+ //!
+ //! let _ = litrs::ByteStringLit::try_from(give::<proc_macro2::Literal>());
+ //! let _ = litrs::ByteStringLit::try_from(give::<&proc_macro2::Literal>());
+ //!
+ //!
+ //! let _ = litrs::BoolLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::BoolLit::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //! let _ = litrs::IntegerLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::IntegerLit::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //! let _ = litrs::FloatLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::FloatLit::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //! let _ = litrs::CharLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::CharLit::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //! let _ = litrs::StringLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::StringLit::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //! let _ = litrs::ByteLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::ByteLit::try_from(give::<&proc_macro2::TokenTree>());
+ //!
+ //! let _ = litrs::ByteStringLit::try_from(give::<proc_macro2::TokenTree>());
+ //! let _ = litrs::ByteStringLit::try_from(give::<&proc_macro2::TokenTree>());
+ //! ```
+}
diff --git a/third_party/rust/litrs/src/integer/mod.rs b/third_party/rust/litrs/src/integer/mod.rs
new file mode 100644
index 0000000000..cecd79d3fb
--- /dev/null
+++ b/third_party/rust/litrs/src/integer/mod.rs
@@ -0,0 +1,356 @@
+use std::{fmt, str::FromStr};
+
+use crate::{
+ Buffer, ParseError,
+ err::{perr, ParseErrorKind::*},
+ parse::{first_byte_or_empty, hex_digit_value, check_suffix},
+};
+
+
+/// An integer literal, e.g. `27`, `0x7F`, `0b101010u8` or `5_000_000i64`.
+///
+/// An integer literal consists of an optional base prefix (`0b`, `0o`, `0x`),
+/// the main part (digits and underscores), and an optional type suffix
+/// (e.g. `u64` or `i8`). See [the reference][ref] for more information.
+///
+/// Note that integer literals are always positive: the grammar does not contain
+/// the minus sign at all. The minus sign is just the unary negate operator,
+/// not part of the literal. Which is interesting for cases like `- 128i8`:
+/// here, the literal itself would overflow the specified type (`i8` cannot
+/// represent 128). That's why in rustc, the literal overflow check is
+/// performed as a lint after parsing, not during the lexing stage. Similarly,
+/// [`IntegerLit::parse`] does not perform an overflow check.
+///
+/// [ref]: https://doc.rust-lang.org/reference/tokens.html#integer-literals
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[non_exhaustive]
+pub struct IntegerLit<B: Buffer> {
+ /// The raw literal. Grammar: `<prefix?><main part><suffix?>`.
+ raw: B,
+ /// First index of the main number part (after the base prefix).
+ start_main_part: usize,
+ /// First index not part of the main number part.
+ end_main_part: usize,
+ /// Parsed `raw[..start_main_part]`.
+ base: IntegerBase,
+}
+
+impl<B: Buffer> IntegerLit<B> {
+ /// Parses the input as an integer literal. Returns an error if the input is
+ /// invalid or represents a different kind of literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ match first_byte_or_empty(&input)? {
+ digit @ b'0'..=b'9' => {
+ // TODO: simplify once RFC 2528 is stabilized
+ let IntegerLit {
+ start_main_part,
+ end_main_part,
+ base,
+ ..
+ } = parse_impl(&input, digit)?;
+
+ Ok(Self { raw: input, start_main_part, end_main_part, base })
+ },
+ _ => Err(perr(0, DoesNotStartWithDigit)),
+ }
+ }
+
+ /// Performs the actual string to int conversion to obtain the integer
+ /// value. The optional type suffix of the literal **is ignored by this
+ /// method**. This means `N` does not need to match the type suffix!
+ ///
+ /// Returns `None` if the literal overflows `N`.
+ ///
+ /// Hint: `u128` can represent all possible values integer literal values,
+ /// as there are no negative literals (see type docs). Thus you can, for
+ /// example, safely use `lit.value::<u128>().to_string()` to get a decimal
+ /// string. (Technically, Rust integer literals can represent arbitrarily
+ /// large numbers, but those would be rejected at a later stage by the Rust
+ /// compiler).
+ pub fn value<N: FromIntegerLiteral>(&self) -> Option<N> {
+ let base = N::from_small_number(self.base.value());
+
+ let mut acc = N::from_small_number(0);
+ for digit in self.raw_main_part().bytes() {
+ if digit == b'_' {
+ continue;
+ }
+
+ // We don't actually need the base here: we already know this main
+ // part only contains digits valid for the specified base.
+ let digit = hex_digit_value(digit)
+ .unwrap_or_else(|| unreachable!("bug: integer main part contains non-digit"));
+
+ acc = acc.checked_mul(base)?;
+ acc = acc.checked_add(N::from_small_number(digit))?;
+ }
+
+ Some(acc)
+ }
+
+ /// The base of this integer literal.
+ pub fn base(&self) -> IntegerBase {
+ self.base
+ }
+
+ /// The main part containing the digits and potentially `_`. Do not try to
+ /// parse this directly as that would ignore the base!
+ pub fn raw_main_part(&self) -> &str {
+ &(*self.raw)[self.start_main_part..self.end_main_part]
+ }
+
+ /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
+ ///
+ /// If you want the type, try `IntegerType::from_suffix(lit.suffix())`.
+ pub fn suffix(&self) -> &str {
+ &(*self.raw)[self.end_main_part..]
+ }
+
+ /// Returns the raw input that was passed to `parse`.
+ pub fn raw_input(&self) -> &str {
+ &self.raw
+ }
+
+ /// Returns the raw input that was passed to `parse`, potentially owned.
+ pub fn into_raw_input(self) -> B {
+ self.raw
+ }
+}
+
+impl IntegerLit<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn to_owned(&self) -> IntegerLit<String> {
+ IntegerLit {
+ raw: self.raw.to_owned(),
+ start_main_part: self.start_main_part,
+ end_main_part: self.end_main_part,
+ base: self.base,
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for IntegerLit<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", &*self.raw)
+ }
+}
+
+/// Integer literal types. *Implementation detail*.
+///
+/// Implemented for all integer literal types. This trait is sealed and cannot
+/// be implemented outside of this crate. The trait's methods are implementation
+/// detail of this library and are not subject to semver.
+pub trait FromIntegerLiteral: self::sealed::Sealed + Copy {
+ /// Creates itself from the given number. `n` is guaranteed to be `<= 16`.
+ #[doc(hidden)]
+ fn from_small_number(n: u8) -> Self;
+
+ #[doc(hidden)]
+ fn checked_add(self, rhs: Self) -> Option<Self>;
+
+ #[doc(hidden)]
+ fn checked_mul(self, rhs: Self) -> Option<Self>;
+
+ #[doc(hidden)]
+ fn ty() -> IntegerType;
+}
+
+macro_rules! impl_from_int_literal {
+ ($( $ty:ty => $variant:ident ,)* ) => {
+ $(
+ impl self::sealed::Sealed for $ty {}
+ impl FromIntegerLiteral for $ty {
+ fn from_small_number(n: u8) -> Self {
+ n as Self
+ }
+ fn checked_add(self, rhs: Self) -> Option<Self> {
+ self.checked_add(rhs)
+ }
+ fn checked_mul(self, rhs: Self) -> Option<Self> {
+ self.checked_mul(rhs)
+ }
+ fn ty() -> IntegerType {
+ IntegerType::$variant
+ }
+ }
+ )*
+ };
+}
+
+impl_from_int_literal!(
+ u8 => U8, u16 => U16, u32 => U32, u64 => U64, u128 => U128, usize => Usize,
+ i8 => I8, i16 => I16, i32 => I32, i64 => I64, i128 => I128, isize => Isize,
+);
+
+mod sealed {
+ pub trait Sealed {}
+}
+
+/// Precondition: first byte of string has to be in `b'0'..=b'9'`.
+#[inline(never)]
+pub(crate) fn parse_impl(input: &str, first: u8) -> Result<IntegerLit<&str>, ParseError> {
+ // Figure out base and strip prefix base, if it exists.
+ let (end_prefix, base) = match (first, input.as_bytes().get(1)) {
+ (b'0', Some(b'b')) => (2, IntegerBase::Binary),
+ (b'0', Some(b'o')) => (2, IntegerBase::Octal),
+ (b'0', Some(b'x')) => (2, IntegerBase::Hexadecimal),
+
+ // Everything else is treated as decimal. Several cases are caught
+ // by this:
+ // - "123"
+ // - "0"
+ // - "0u8"
+ // - "0r" -> this will error later
+ _ => (0, IntegerBase::Decimal),
+ };
+ let without_prefix = &input[end_prefix..];
+
+
+ // Scan input to find the first character that's not a valid digit.
+ let is_valid_digit = match base {
+ IntegerBase::Binary => |b| matches!(b, b'0' | b'1' | b'_'),
+ IntegerBase::Octal => |b| matches!(b, b'0'..=b'7' | b'_'),
+ IntegerBase::Decimal => |b| matches!(b, b'0'..=b'9' | b'_'),
+ IntegerBase::Hexadecimal => |b| matches!(b, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' | b'_'),
+ };
+ let end_main = without_prefix.bytes()
+ .position(|b| !is_valid_digit(b))
+ .unwrap_or(without_prefix.len());
+ let (main_part, suffix) = without_prefix.split_at(end_main);
+
+ check_suffix(suffix).map_err(|kind| {
+ // This is just to have a nicer error kind for this special case. If the
+ // suffix is invalid, it is non-empty -> unwrap ok.
+ let first = suffix.as_bytes()[0];
+ if !is_valid_digit(first) && first.is_ascii_digit() {
+ perr(end_main + end_prefix, InvalidDigit)
+ } else {
+ perr(end_main + end_prefix..input.len(), kind)
+ }
+ })?;
+ if suffix.starts_with('e') || suffix.starts_with('E') {
+ return Err(perr(end_main, IntegerSuffixStartingWithE));
+ }
+
+ // Make sure main number part is not empty.
+ if main_part.bytes().filter(|&b| b != b'_').count() == 0 {
+ return Err(perr(end_prefix..end_prefix + end_main, NoDigits));
+ }
+
+ Ok(IntegerLit {
+ raw: input,
+ start_main_part: end_prefix,
+ end_main_part: end_main + end_prefix,
+ base,
+ })
+}
+
+
+/// The bases in which an integer can be specified.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum IntegerBase {
+ Binary,
+ Octal,
+ Decimal,
+ Hexadecimal,
+}
+
+impl IntegerBase {
+ /// Returns the literal prefix that indicates this base, i.e. `"0b"`,
+ /// `"0o"`, `""` and `"0x"`.
+ pub fn prefix(self) -> &'static str {
+ match self {
+ Self::Binary => "0b",
+ Self::Octal => "0o",
+ Self::Decimal => "",
+ Self::Hexadecimal => "0x",
+ }
+ }
+
+ /// Returns the base value, i.e. 2, 8, 10 or 16.
+ pub fn value(self) -> u8 {
+ match self {
+ Self::Binary => 2,
+ Self::Octal => 8,
+ Self::Decimal => 10,
+ Self::Hexadecimal => 16,
+ }
+ }
+}
+
+/// All possible integer type suffixes.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum IntegerType {
+ U8,
+ U16,
+ U32,
+ U64,
+ U128,
+ Usize,
+ I8,
+ I16,
+ I32,
+ I64,
+ I128,
+ Isize,
+}
+
+impl IntegerType {
+ /// Returns the type corresponding to the given suffix (e.g. `"u8"` is
+ /// mapped to `Self::U8`). If the suffix is not a valid integer type,
+ /// `None` is returned.
+ pub fn from_suffix(suffix: &str) -> Option<Self> {
+ match suffix {
+ "u8" => Some(Self::U8),
+ "u16" => Some(Self::U16),
+ "u32" => Some(Self::U32),
+ "u64" => Some(Self::U64),
+ "u128" => Some(Self::U128),
+ "usize" => Some(Self::Usize),
+ "i8" => Some(Self::I8),
+ "i16" => Some(Self::I16),
+ "i32" => Some(Self::I32),
+ "i64" => Some(Self::I64),
+ "i128" => Some(Self::I128),
+ "isize" => Some(Self::Isize),
+ _ => None,
+ }
+ }
+
+ /// Returns the suffix for this type, e.g. `"u8"` for `Self::U8`.
+ pub fn suffix(self) -> &'static str {
+ match self {
+ Self::U8 => "u8",
+ Self::U16 => "u16",
+ Self::U32 => "u32",
+ Self::U64 => "u64",
+ Self::U128 => "u128",
+ Self::Usize => "usize",
+ Self::I8 => "i8",
+ Self::I16 => "i16",
+ Self::I32 => "i32",
+ Self::I64 => "i64",
+ Self::I128 => "i128",
+ Self::Isize => "isize",
+ }
+ }
+}
+
+impl FromStr for IntegerType {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Self::from_suffix(s).ok_or(())
+ }
+}
+
+impl fmt::Display for IntegerType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.suffix().fmt(f)
+ }
+}
+
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/integer/tests.rs b/third_party/rust/litrs/src/integer/tests.rs
new file mode 100644
index 0000000000..e6dad3f031
--- /dev/null
+++ b/third_party/rust/litrs/src/integer/tests.rs
@@ -0,0 +1,357 @@
+use std::fmt::{Debug, Display};
+use crate::{
+ FromIntegerLiteral, Literal, IntegerLit, IntegerType as Ty, IntegerBase, IntegerBase::*,
+ test_util::{assert_parse_ok_eq, assert_roundtrip},
+};
+
+
+// ===== Utility functions =======================================================================
+
+#[track_caller]
+fn check<T: FromIntegerLiteral + PartialEq + Debug + Display>(
+ input: &str,
+ value: T,
+ base: IntegerBase,
+ main_part: &str,
+ type_suffix: Option<Ty>,
+) {
+ let expected_integer = IntegerLit {
+ raw: input,
+ start_main_part: base.prefix().len(),
+ end_main_part: base.prefix().len() + main_part.len(),
+ base,
+ };
+ assert_parse_ok_eq(
+ input, IntegerLit::parse(input), expected_integer.clone(), "IntegerLit::parse");
+ assert_parse_ok_eq(
+ input, Literal::parse(input), Literal::Integer(expected_integer), "Literal::parse");
+ assert_roundtrip(expected_integer.to_owned(), input);
+ assert_eq!(Ty::from_suffix(IntegerLit::parse(input).unwrap().suffix()), type_suffix);
+
+ let actual_value = IntegerLit::parse(input)
+ .unwrap()
+ .value::<T>()
+ .unwrap_or_else(|| panic!("unexpected overflow in `IntegerLit::value` for `{}`", input));
+ if actual_value != value {
+ panic!(
+ "Parsing int literal `{}` should give value `{}`, but actually resulted in `{}`",
+ input,
+ value,
+ actual_value,
+ );
+ }
+}
+
+
+// ===== Actual tests ===========================================================================
+
+#[test]
+fn parse_decimal() {
+ check("0", 0u128, Decimal, "0", None);
+ check("1", 1u8, Decimal, "1", None);
+ check("8", 8u16, Decimal, "8", None);
+ check("9", 9u32, Decimal, "9", None);
+ check("10", 10u64, Decimal, "10", None);
+ check("11", 11i8, Decimal, "11", None);
+ check("123456789", 123456789i128, Decimal, "123456789", None);
+
+ check("05", 5i16, Decimal, "05", None);
+ check("00005", 5i32, Decimal, "00005", None);
+ check("0123456789", 123456789i64, Decimal, "0123456789", None);
+
+ check("123_456_789", 123_456_789, Decimal, "123_456_789", None);
+ check("0___4", 4, Decimal, "0___4", None);
+ check("0___4_3", 43, Decimal, "0___4_3", None);
+ check("0___4_3", 43, Decimal, "0___4_3", None);
+ check("123___________", 123, Decimal, "123___________", None);
+
+ check(
+ "340282366920938463463374607431768211455",
+ 340282366920938463463374607431768211455u128,
+ Decimal,
+ "340282366920938463463374607431768211455",
+ None,
+ );
+ check(
+ "340_282_366_920_938_463_463_374_607_431_768_211_455",
+ 340282366920938463463374607431768211455u128,
+ Decimal,
+ "340_282_366_920_938_463_463_374_607_431_768_211_455",
+ None,
+ );
+ check(
+ "3_40_282_3669_20938_463463_3746074_31768211_455___",
+ 340282366920938463463374607431768211455u128,
+ Decimal,
+ "3_40_282_3669_20938_463463_3746074_31768211_455___",
+ None,
+ );
+}
+
+#[test]
+fn parse_binary() {
+ check("0b0", 0b0, Binary, "0", None);
+ check("0b000", 0b000, Binary, "000", None);
+ check("0b1", 0b1, Binary, "1", None);
+ check("0b01", 0b01, Binary, "01", None);
+ check("0b101010", 0b101010, Binary, "101010", None);
+ check("0b10_10_10", 0b10_10_10, Binary, "10_10_10", None);
+ check("0b01101110____", 0b01101110____, Binary, "01101110____", None);
+
+ check("0b10010u8", 0b10010u8, Binary, "10010", Some(Ty::U8));
+ check("0b10010i8", 0b10010u8, Binary, "10010", Some(Ty::I8));
+ check("0b10010u64", 0b10010u64, Binary, "10010", Some(Ty::U64));
+ check("0b10010i64", 0b10010i64, Binary, "10010", Some(Ty::I64));
+ check(
+ "0b1011001_00110000_00101000_10100101u32",
+ 0b1011001_00110000_00101000_10100101u32,
+ Binary,
+ "1011001_00110000_00101000_10100101",
+ Some(Ty::U32),
+ );
+}
+
+#[test]
+fn parse_octal() {
+ check("0o0", 0o0, Octal, "0", None);
+ check("0o1", 0o1, Octal, "1", None);
+ check("0o6", 0o6, Octal, "6", None);
+ check("0o7", 0o7, Octal, "7", None);
+ check("0o17", 0o17, Octal, "17", None);
+ check("0o123", 0o123, Octal, "123", None);
+ check("0o7654321", 0o7654321, Octal, "7654321", None);
+ check("0o7_53_1", 0o7_53_1, Octal, "7_53_1", None);
+ check("0o66_", 0o66_, Octal, "66_", None);
+
+ check("0o755u16", 0o755u16, Octal, "755", Some(Ty::U16));
+ check("0o755i128", 0o755i128, Octal, "755", Some(Ty::I128));
+}
+
+#[test]
+fn parse_hexadecimal() {
+ check("0x0", 0x0, Hexadecimal, "0", None);
+ check("0x1", 0x1, Hexadecimal, "1", None);
+ check("0x9", 0x9, Hexadecimal, "9", None);
+
+ check("0xa", 0xa, Hexadecimal, "a", None);
+ check("0xf", 0xf, Hexadecimal, "f", None);
+ check("0x17", 0x17, Hexadecimal, "17", None);
+ check("0x1b", 0x1b, Hexadecimal, "1b", None);
+ check("0x123", 0x123, Hexadecimal, "123", None);
+ check("0xace", 0xace, Hexadecimal, "ace", None);
+ check("0xfdb971", 0xfdb971, Hexadecimal, "fdb971", None);
+ check("0xa_54_f", 0xa_54_f, Hexadecimal, "a_54_f", None);
+ check("0x6d_", 0x6d_, Hexadecimal, "6d_", None);
+
+ check("0xA", 0xA, Hexadecimal, "A", None);
+ check("0xF", 0xF, Hexadecimal, "F", None);
+ check("0x17", 0x17, Hexadecimal, "17", None);
+ check("0x1B", 0x1B, Hexadecimal, "1B", None);
+ check("0x123", 0x123, Hexadecimal, "123", None);
+ check("0xACE", 0xACE, Hexadecimal, "ACE", None);
+ check("0xFDB971", 0xFDB971, Hexadecimal, "FDB971", None);
+ check("0xA_54_F", 0xA_54_F, Hexadecimal, "A_54_F", None);
+ check("0x6D_", 0x6D_, Hexadecimal, "6D_", None);
+
+ check("0xFdB97a1", 0xFdB97a1, Hexadecimal, "FdB97a1", None);
+ check("0xfdB97A1", 0xfdB97A1, Hexadecimal, "fdB97A1", None);
+
+ check("0x40u16", 0x40u16, Hexadecimal, "40", Some(Ty::U16));
+ check("0xffi128", 0xffi128, Hexadecimal, "ff", Some(Ty::I128));
+}
+
+#[test]
+fn starting_underscore() {
+ check("0b_1", 1, Binary, "_1", None);
+ check("0b_010i16", 0b_010, Binary, "_010", Some(Ty::I16));
+
+ check("0o_5", 5, Octal, "_5", None);
+ check("0o_750u128", 0o_750u128, Octal, "_750", Some(Ty::U128));
+
+ check("0x_c", 0xc, Hexadecimal, "_c", None);
+ check("0x_cf3i8", 0x_cf3, Hexadecimal, "_cf3", Some(Ty::I8));
+}
+
+#[test]
+fn parse_overflowing_just_fine() {
+ check("256u8", 256u16, Decimal, "256", Some(Ty::U8));
+ check("123_456_789u8", 123_456_789u32, Decimal, "123_456_789", Some(Ty::U8));
+ check("123_456_789u16", 123_456_789u32, Decimal, "123_456_789", Some(Ty::U16));
+
+ check("123_123_456_789u8", 123_123_456_789u64, Decimal, "123_123_456_789", Some(Ty::U8));
+ check("123_123_456_789u16", 123_123_456_789u64, Decimal, "123_123_456_789", Some(Ty::U16));
+ check("123_123_456_789u32", 123_123_456_789u64, Decimal, "123_123_456_789", Some(Ty::U32));
+}
+
+#[test]
+fn suffixes() {
+ [
+ ("123i8", Ty::I8),
+ ("123i16", Ty::I16),
+ ("123i32", Ty::I32),
+ ("123i64", Ty::I64),
+ ("123i128", Ty::I128),
+ ("123u8", Ty::U8),
+ ("123u16", Ty::U16),
+ ("123u32", Ty::U32),
+ ("123u64", Ty::U64),
+ ("123u128", Ty::U128),
+ ].iter().for_each(|&(s, ty)| {
+ assert_eq!(Ty::from_suffix(IntegerLit::parse(s).unwrap().suffix()), Some(ty));
+ });
+}
+
+#[test]
+fn overflow_u128() {
+ let inputs = [
+ "340282366920938463463374607431768211456",
+ "0x100000000000000000000000000000000",
+ "0o4000000000000000000000000000000000000000000",
+ "0b1000000000000000000000000000000000000000000000000000000000000000000\
+ 00000000000000000000000000000000000000000000000000000000000000",
+ "340282366920938463463374607431768211456u128",
+ "340282366920938463463374607431768211457",
+ "3_40_282_3669_20938_463463_3746074_31768211_456___",
+ "3_40_282_3669_20938_463463_3746074_31768211_455___1",
+ "3_40_282_3669_20938_463463_3746074_31768211_455___0u128",
+ "3402823669209384634633746074317682114570",
+ ];
+
+ for &input in &inputs {
+ let lit = IntegerLit::parse(input).expect("failed to parse");
+ assert!(lit.value::<u128>().is_none());
+ }
+}
+
+#[test]
+fn overflow_u8() {
+ let inputs = [
+ "256", "0x100", "0o400", "0b100000000",
+ "257", "0x101", "0o401", "0b100000001",
+ "300",
+ "1548",
+ "2548985",
+ "256u128",
+ "256u8",
+ "2_5_6",
+ "256_____1",
+ "256__",
+ ];
+
+ for &input in &inputs {
+ let lit = IntegerLit::parse(input).expect("failed to parse");
+ assert!(lit.value::<u8>().is_none());
+ }
+}
+
+#[test]
+fn parse_err() {
+ assert_err!(IntegerLit, "", Empty, None);
+ assert_err_single!(IntegerLit::parse("a"), DoesNotStartWithDigit, 0);
+ assert_err_single!(IntegerLit::parse(";"), DoesNotStartWithDigit, 0);
+ assert_err_single!(IntegerLit::parse("0;"), UnexpectedChar, 1..2);
+ assert_err!(IntegerLit, "0b", NoDigits, 2..2);
+ assert_err_single!(IntegerLit::parse(" 0"), DoesNotStartWithDigit, 0);
+ assert_err_single!(IntegerLit::parse("0 "), UnexpectedChar, 1);
+ assert_err!(IntegerLit, "0b3", InvalidDigit, 2);
+ assert_err_single!(IntegerLit::parse("_"), DoesNotStartWithDigit, 0);
+ assert_err_single!(IntegerLit::parse("_3"), DoesNotStartWithDigit, 0);
+ assert_err!(IntegerLit, "0x44.5", UnexpectedChar, 4..6);
+ assert_err_single!(IntegerLit::parse("123em"), IntegerSuffixStartingWithE, 3);
+}
+
+#[test]
+fn invalid_digits() {
+ assert_err!(IntegerLit, "0b10201", InvalidDigit, 4);
+ assert_err!(IntegerLit, "0b9", InvalidDigit, 2);
+ assert_err!(IntegerLit, "0b07", InvalidDigit, 3);
+
+ assert_err!(IntegerLit, "0o12380", InvalidDigit, 5);
+ assert_err!(IntegerLit, "0o192", InvalidDigit, 3);
+
+ assert_err_single!(IntegerLit::parse("a_123"), DoesNotStartWithDigit, 0);
+ assert_err_single!(IntegerLit::parse("B_123"), DoesNotStartWithDigit, 0);
+}
+
+#[test]
+fn no_valid_digits() {
+ assert_err!(IntegerLit, "0x_", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0x__", NoDigits, 2..4);
+ assert_err!(IntegerLit, "0x________", NoDigits, 2..10);
+ assert_err!(IntegerLit, "0x_i8", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0x_u8", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0x_isize", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0x_usize", NoDigits, 2..3);
+
+ assert_err!(IntegerLit, "0o_", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0o__", NoDigits, 2..4);
+ assert_err!(IntegerLit, "0o________", NoDigits, 2..10);
+ assert_err!(IntegerLit, "0o_i32", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0o_u32", NoDigits, 2..3);
+
+ assert_err!(IntegerLit, "0b_", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0b__", NoDigits, 2..4);
+ assert_err!(IntegerLit, "0b________", NoDigits, 2..10);
+ assert_err!(IntegerLit, "0b_i128", NoDigits, 2..3);
+ assert_err!(IntegerLit, "0b_u128", NoDigits, 2..3);
+}
+
+#[test]
+fn non_standard_suffixes() {
+ #[track_caller]
+ fn check_suffix<T: FromIntegerLiteral + PartialEq + Debug + Display>(
+ input: &str,
+ value: T,
+ base: IntegerBase,
+ main_part: &str,
+ suffix: &str,
+ ) {
+ check(input, value, base, main_part, None);
+ assert_eq!(IntegerLit::parse(input).unwrap().suffix(), suffix);
+ }
+
+ check_suffix("5u7", 5, Decimal, "5", "u7");
+ check_suffix("5u7", 5, Decimal, "5", "u7");
+ check_suffix("5u9", 5, Decimal, "5", "u9");
+ check_suffix("5u0", 5, Decimal, "5", "u0");
+ check_suffix("33u12", 33, Decimal, "33", "u12");
+ check_suffix("84u17", 84, Decimal, "84", "u17");
+ check_suffix("99u80", 99, Decimal, "99", "u80");
+ check_suffix("1234uu16", 1234, Decimal, "1234", "uu16");
+
+ check_suffix("5i7", 5, Decimal, "5", "i7");
+ check_suffix("5i9", 5, Decimal, "5", "i9");
+ check_suffix("5i0", 5, Decimal, "5", "i0");
+ check_suffix("33i12", 33, Decimal, "33", "i12");
+ check_suffix("84i17", 84, Decimal, "84", "i17");
+ check_suffix("99i80", 99, Decimal, "99", "i80");
+ check_suffix("1234ii16", 1234, Decimal, "1234", "ii16");
+
+ check_suffix("0ui32", 0, Decimal, "0", "ui32");
+ check_suffix("1iu32", 1, Decimal, "1", "iu32");
+ check_suffix("54321a64", 54321, Decimal, "54321", "a64");
+ check_suffix("54321b64", 54321, Decimal, "54321", "b64");
+ check_suffix("54321x64", 54321, Decimal, "54321", "x64");
+ check_suffix("54321o64", 54321, Decimal, "54321", "o64");
+
+ check_suffix("0a", 0, Decimal, "0", "a");
+ check_suffix("0a3", 0, Decimal, "0", "a3");
+ check_suffix("0z", 0, Decimal, "0", "z");
+ check_suffix("0z3", 0, Decimal, "0", "z3");
+ check_suffix("0b0a", 0, Binary, "0", "a");
+ check_suffix("0b0A", 0, Binary, "0", "A");
+ check_suffix("0b01f", 1, Binary, "01", "f");
+ check_suffix("0b01F", 1, Binary, "01", "F");
+ check_suffix("0o7a_", 7, Octal, "7", "a_");
+ check_suffix("0o7A_", 7, Octal, "7", "A_");
+ check_suffix("0o72f_0", 0o72, Octal, "72", "f_0");
+ check_suffix("0o72F_0", 0o72, Octal, "72", "F_0");
+
+ check_suffix("0x8cg", 0x8c, Hexadecimal, "8c", "g");
+ check_suffix("0x8cG", 0x8c, Hexadecimal, "8c", "G");
+ check_suffix("0x8c1h_", 0x8c1, Hexadecimal, "8c1", "h_");
+ check_suffix("0x8c1H_", 0x8c1, Hexadecimal, "8c1", "H_");
+ check_suffix("0x8czu16", 0x8c, Hexadecimal, "8c", "zu16");
+
+ check_suffix("123_foo", 123, Decimal, "123_", "foo");
+}
diff --git a/third_party/rust/litrs/src/lib.rs b/third_party/rust/litrs/src/lib.rs
new file mode 100644
index 0000000000..64ed7813c9
--- /dev/null
+++ b/third_party/rust/litrs/src/lib.rs
@@ -0,0 +1,370 @@
+//! Parsing and inspecting Rust literal tokens.
+//!
+//! This library offers functionality to parse Rust literals, i.e. tokens in the
+//! Rust programming language that represent fixed values. The grammar for
+//! those is defined [here][ref].
+//!
+//! This kind of functionality already exists in the crate `syn`. However, as
+//! you oftentimes don't need (nor want) the full power of `syn`, `litrs` was
+//! built. This crate also offers a bit more flexibility compared to `syn`
+//! (only regarding literals, of course).
+//!
+//!
+//! # Quick start
+//!
+//! | **`StringLit::try_from(tt)?.value()`** |
+//! | - |
+//!
+//! ... where `tt` is a `proc_macro::TokenTree` and where [`StringLit`] can be
+//! replaced with [`Literal`] or other types of literals (e.g. [`FloatLit`]).
+//! Calling `value()` returns the value that is represented by the literal.
+//!
+//! **Mini Example**
+//!
+//! ```ignore
+//! use proc_macro::TokenStream;
+//!
+//! #[proc_macro]
+//! pub fn foo(input: TokenStream) -> TokenStream {
+//! let first_token = input.into_iter().next().unwrap(); // Do proper error handling!
+//! let string_value = match litrs::StringLit::try_from(first_token) {
+//! Ok(string_lit) => string_lit.value(),
+//! Err(e) => return e.to_compile_error(),
+//! };
+//!
+//! // `string_value` is the string value with all escapes resolved.
+//! todo!()
+//! }
+//! ```
+//!
+//! # Overview
+//!
+//! The main types of this library are [`Literal`], representing any kind of
+//! literal, and `*Lit`, like [`StringLit`] or [`FloatLit`], representing a
+//! specific kind of literal.
+//!
+//! There are different ways to obtain such a literal type:
+//!
+//! - **`parse`**: parses a `&str` or `String` and returns `Result<_,
+//! ParseError>`. For example: [`Literal::parse`] and
+//! [`IntegerLit::parse`].
+//!
+//! - **`From<proc_macro::Literal> for Literal`**: turns a `Literal` value from
+//! the `proc_macro` crate into a `Literal` from this crate.
+//!
+//! - **`TryFrom<proc_macro::Literal> for *Lit`**: tries to turn a
+//! `proc_macro::Literal` into a specific literal type of this crate. If
+//! the input is a literal of a different kind, `Err(InvalidToken)` is
+//! returned.
+//!
+//! - **`TryFrom<proc_macro::TokenTree>`**: attempts to turn a token tree into a
+//! literal type of this crate. An error is returned if the token tree is
+//! not a literal, or if you are trying to turn it into a specific kind of
+//! literal and the token tree is a different kind of literal.
+//!
+//! All of the `From` and `TryFrom` conversions also work for reference to
+//! `proc_macro` types. Additionally, if the crate feature `proc-macro2` is
+//! enabled (which it is by default), all these `From` and `TryFrom` impls also
+//! exist for the corresponding `proc_macro2` types.
+//!
+//! **Note**: `true` and `false` are `Ident`s when passed to your proc macro.
+//! The `TryFrom<TokenTree>` impls check for those two special idents and
+//! return a [`BoolLit`] appropriately. For that reason, there is also no
+//! `TryFrom<proc_macro::Literal>` impl for [`BoolLit`]. The `proc_macro::Literal`
+//! simply cannot represent bool literals.
+//!
+//!
+//! # Examples
+//!
+//! In a proc-macro:
+//!
+//! ```ignore
+//! use std::convert::TryFrom;
+//! use proc_macro::TokenStream;
+//! use litrs::FloatLit;
+//!
+//! #[proc_macro]
+//! pub fn foo(input: TokenStream) -> TokenStream {
+//! let mut input = input.into_iter().collect::<Vec<_>>();
+//! if input.len() != 1 {
+//! // Please do proper error handling in your real code!
+//! panic!("expected exactly one token as input");
+//! }
+//! let token = input.remove(0);
+//!
+//! match FloatLit::try_from(token) {
+//! Ok(float_lit) => { /* do something */ }
+//! Err(e) => return e.to_compile_error(),
+//! }
+//!
+//! // Dummy output
+//! TokenStream::new()
+//! }
+//! ```
+//!
+//! Parsing from string:
+//!
+//! ```
+//! use litrs::{FloatLit, Literal};
+//!
+//! // Parse a specific kind of literal (float in this case):
+//! let float_lit = FloatLit::parse("3.14f32");
+//! assert!(float_lit.is_ok());
+//! assert_eq!(float_lit.unwrap().suffix(), "f32");
+//! assert!(FloatLit::parse("'c'").is_err());
+//!
+//! // Parse any kind of literal. After parsing, you can inspect the literal
+//! // and decide what to do in each case.
+//! let lit = Literal::parse("0xff80").expect("failed to parse literal");
+//! match lit {
+//! Literal::Integer(lit) => { /* ... */ }
+//! Literal::Float(lit) => { /* ... */ }
+//! Literal::Bool(lit) => { /* ... */ }
+//! Literal::Char(lit) => { /* ... */ }
+//! Literal::String(lit) => { /* ... */ }
+//! Literal::Byte(lit) => { /* ... */ }
+//! Literal::ByteString(lit) => { /* ... */ }
+//! }
+//! ```
+//!
+//!
+//!
+//! # Crate features
+//!
+//! - `proc-macro2` (**default**): adds the dependency `proc_macro2`, a bunch of
+//! `From` and `TryFrom` impls, and [`InvalidToken::to_compile_error2`].
+//! - `check_suffix`: if enabled, `parse` functions will exactly verify that the
+//! literal suffix is valid. Adds the dependency `unicode-xid`. If disabled,
+//! only an approximate check (only in ASCII range) is done. If you are
+//! writing a proc macro, you don't need to enable this as the suffix is
+//! already checked by the compiler.
+//!
+//!
+//! [ref]: https://doc.rust-lang.org/reference/tokens.html#literals
+//!
+
+#![deny(missing_debug_implementations)]
+
+extern crate proc_macro;
+
+#[cfg(test)]
+#[macro_use]
+mod test_util;
+
+#[cfg(test)]
+mod tests;
+
+mod bool;
+mod byte;
+mod bytestr;
+mod char;
+mod err;
+mod escape;
+mod float;
+mod impls;
+mod integer;
+mod parse;
+mod string;
+
+
+use std::{borrow::{Borrow, Cow}, fmt, ops::{Deref, Range}};
+
+pub use self::{
+ bool::BoolLit,
+ byte::ByteLit,
+ bytestr::ByteStringLit,
+ char::CharLit,
+ err::{InvalidToken, ParseError},
+ float::{FloatLit, FloatType},
+ integer::{FromIntegerLiteral, IntegerLit, IntegerBase, IntegerType},
+ string::StringLit,
+};
+
+
+// ==============================================================================================
+// ===== `Literal` and type defs
+// ==============================================================================================
+
+/// A literal. This is the main type of this library.
+///
+/// This type is generic over the underlying buffer `B`, which can be `&str` or
+/// `String`.
+///
+/// To create this type, you have to either call [`Literal::parse`] with an
+/// input string or use the `From<_>` impls of this type. The impls are only
+/// available of the corresponding crate features are enabled (they are enabled
+/// by default).
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Literal<B: Buffer> {
+ Bool(BoolLit),
+ Integer(IntegerLit<B>),
+ Float(FloatLit<B>),
+ Char(CharLit<B>),
+ String(StringLit<B>),
+ Byte(ByteLit<B>),
+ ByteString(ByteStringLit<B>),
+}
+
+impl<B: Buffer> Literal<B> {
+ /// Parses the given input as a Rust literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ parse::parse(input)
+ }
+
+ /// Returns the suffix of this literal or `""` if it doesn't have one.
+ ///
+ /// Rust token grammar actually allows suffixes for all kinds of tokens.
+ /// Most Rust programmer only know the type suffixes for integer and
+ /// floats, e.g. `0u32`. And in normal Rust code, everything else causes an
+ /// error. But it is possible to pass literals with arbitrary suffixes to
+ /// proc macros, for example:
+ ///
+ /// ```ignore
+ /// some_macro!(3.14f33 16px '🦊'good_boy "toph"beifong);
+ /// ```
+ ///
+ /// Boolean literals, not actually being literals, but idents, cannot have
+ /// suffixes and this method always returns `""` for those.
+ ///
+ /// There are some edge cases to be aware of:
+ /// - Integer suffixes must not start with `e` or `E` as that conflicts with
+ /// the exponent grammar for floats. `0e1` is a float; `0eel` is also
+ /// parsed as a float and results in an error.
+ /// - Hexadecimal integers eagerly parse digits, so `0x5abcdefgh` has a
+ /// suffix von `gh`.
+ /// - Suffixes can contain and start with `_`, but for integer and number
+ /// literals, `_` is eagerly parsed as part of the number, so `1_x` has
+ /// the suffix `x`.
+ /// - The input `55f32` is regarded as integer literal with suffix `f32`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use litrs::Literal;
+ ///
+ /// assert_eq!(Literal::parse(r##"3.14f33"##).unwrap().suffix(), "f33");
+ /// assert_eq!(Literal::parse(r##"123hackerman"##).unwrap().suffix(), "hackerman");
+ /// assert_eq!(Literal::parse(r##"0x0fuck"##).unwrap().suffix(), "uck");
+ /// assert_eq!(Literal::parse(r##"'🦊'good_boy"##).unwrap().suffix(), "good_boy");
+ /// assert_eq!(Literal::parse(r##""toph"beifong"##).unwrap().suffix(), "beifong");
+ /// ```
+ pub fn suffix(&self) -> &str {
+ match self {
+ Literal::Bool(_) => "",
+ Literal::Integer(l) => l.suffix(),
+ Literal::Float(l) => l.suffix(),
+ Literal::Char(l) => l.suffix(),
+ Literal::String(l) => l.suffix(),
+ Literal::Byte(l) => l.suffix(),
+ Literal::ByteString(l) => l.suffix(),
+ }
+ }
+}
+
+impl Literal<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn into_owned(self) -> Literal<String> {
+ match self {
+ Literal::Bool(l) => Literal::Bool(l.to_owned()),
+ Literal::Integer(l) => Literal::Integer(l.to_owned()),
+ Literal::Float(l) => Literal::Float(l.to_owned()),
+ Literal::Char(l) => Literal::Char(l.to_owned()),
+ Literal::String(l) => Literal::String(l.into_owned()),
+ Literal::Byte(l) => Literal::Byte(l.to_owned()),
+ Literal::ByteString(l) => Literal::ByteString(l.into_owned()),
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for Literal<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Literal::Bool(l) => l.fmt(f),
+ Literal::Integer(l) => l.fmt(f),
+ Literal::Float(l) => l.fmt(f),
+ Literal::Char(l) => l.fmt(f),
+ Literal::String(l) => l.fmt(f),
+ Literal::Byte(l) => l.fmt(f),
+ Literal::ByteString(l) => l.fmt(f),
+ }
+ }
+}
+
+
+// ==============================================================================================
+// ===== Buffer
+// ==============================================================================================
+
+/// A shared or owned string buffer. Implemented for `String` and `&str`. *Implementation detail*.
+///
+/// This is trait is implementation detail of this library, cannot be
+/// implemented in other crates and is not subject to semantic versioning.
+/// `litrs` only guarantees that this trait is implemented for `String` and
+/// `for<'a> &'a str`.
+pub trait Buffer: sealed::Sealed + Deref<Target = str> {
+ /// This is `Cow<'static, str>` for `String`, and `Cow<'a, str>` for `&'a str`.
+ type Cow: From<String> + AsRef<str> + Borrow<str> + Deref<Target = str>;
+
+ #[doc(hidden)]
+ fn into_cow(self) -> Self::Cow;
+
+ /// This is `Cow<'static, [u8]>` for `String`, and `Cow<'a, [u8]>` for `&'a str`.
+ type ByteCow: From<Vec<u8>> + AsRef<[u8]> + Borrow<[u8]> + Deref<Target = [u8]>;
+
+ #[doc(hidden)]
+ fn into_byte_cow(self) -> Self::ByteCow;
+
+ /// Cuts away some characters at the beginning and some at the end. Given
+ /// range has to be in bounds.
+ #[doc(hidden)]
+ fn cut(self, range: Range<usize>) -> Self;
+}
+
+mod sealed {
+ pub trait Sealed {}
+}
+
+impl<'a> sealed::Sealed for &'a str {}
+impl<'a> Buffer for &'a str {
+ #[doc(hidden)]
+ fn cut(self, range: Range<usize>) -> Self {
+ &self[range]
+ }
+
+ type Cow = Cow<'a, str>;
+ #[doc(hidden)]
+ fn into_cow(self) -> Self::Cow {
+ self.into()
+ }
+ type ByteCow = Cow<'a, [u8]>;
+ #[doc(hidden)]
+ fn into_byte_cow(self) -> Self::ByteCow {
+ self.as_bytes().into()
+ }
+}
+
+impl sealed::Sealed for String {}
+impl Buffer for String {
+ #[doc(hidden)]
+ fn cut(mut self, range: Range<usize>) -> Self {
+ // This is not the most efficient way, but it works. First we cut the
+ // end, then the beginning. Note that `drain` also removes the range if
+ // the iterator is not consumed.
+ self.truncate(range.end);
+ self.drain(..range.start);
+ self
+ }
+
+ type Cow = Cow<'static, str>;
+ #[doc(hidden)]
+ fn into_cow(self) -> Self::Cow {
+ self.into()
+ }
+
+ type ByteCow = Cow<'static, [u8]>;
+ #[doc(hidden)]
+ fn into_byte_cow(self) -> Self::ByteCow {
+ self.into_bytes().into()
+ }
+}
diff --git a/third_party/rust/litrs/src/parse.rs b/third_party/rust/litrs/src/parse.rs
new file mode 100644
index 0000000000..efc6b870f6
--- /dev/null
+++ b/third_party/rust/litrs/src/parse.rs
@@ -0,0 +1,125 @@
+use crate::{
+ BoolLit,
+ Buffer,
+ ByteLit,
+ ByteStringLit,
+ CharLit,
+ ParseError,
+ FloatLit,
+ IntegerLit,
+ Literal,
+ StringLit,
+ err::{perr, ParseErrorKind::{*, self}},
+};
+
+
+pub fn parse<B: Buffer>(input: B) -> Result<Literal<B>, ParseError> {
+ let (first, rest) = input.as_bytes().split_first().ok_or(perr(None, Empty))?;
+ let second = input.as_bytes().get(1).copied();
+
+ match first {
+ b'f' if &*input == "false" => Ok(Literal::Bool(BoolLit::False)),
+ b't' if &*input == "true" => Ok(Literal::Bool(BoolLit::True)),
+
+ // A number literal (integer or float).
+ b'0'..=b'9' => {
+ // To figure out whether this is a float or integer, we do some
+ // quick inspection here. Yes, this is technically duplicate
+ // work with what is happening in the integer/float parse
+ // methods, but it makes the code way easier for now and won't
+ // be a huge performance loss.
+ //
+ // The first non-decimal char in a float literal must
+ // be '.', 'e' or 'E'.
+ match input.as_bytes().get(1 + end_dec_digits(rest)) {
+ Some(b'.') | Some(b'e') | Some(b'E')
+ => FloatLit::parse(input).map(Literal::Float),
+
+ _ => IntegerLit::parse(input).map(Literal::Integer),
+ }
+ },
+
+ b'\'' => CharLit::parse(input).map(Literal::Char),
+ b'"' | b'r' => StringLit::parse(input).map(Literal::String),
+
+ b'b' if second == Some(b'\'') => ByteLit::parse(input).map(Literal::Byte),
+ b'b' if second == Some(b'r') || second == Some(b'"')
+ => ByteStringLit::parse(input).map(Literal::ByteString),
+
+ _ => Err(perr(None, InvalidLiteral)),
+ }
+}
+
+
+pub(crate) fn first_byte_or_empty(s: &str) -> Result<u8, ParseError> {
+ s.as_bytes().get(0).copied().ok_or(perr(None, Empty))
+}
+
+/// Returns the index of the first non-underscore, non-decimal digit in `input`,
+/// or the `input.len()` if all characters are decimal digits.
+pub(crate) fn end_dec_digits(input: &[u8]) -> usize {
+ input.iter()
+ .position(|b| !matches!(b, b'_' | b'0'..=b'9'))
+ .unwrap_or(input.len())
+}
+
+pub(crate) fn hex_digit_value(digit: u8) -> Option<u8> {
+ match digit {
+ b'0'..=b'9' => Some(digit - b'0'),
+ b'a'..=b'f' => Some(digit - b'a' + 10),
+ b'A'..=b'F' => Some(digit - b'A' + 10),
+ _ => None,
+ }
+}
+
+/// Makes sure that `s` is a valid literal suffix.
+pub(crate) fn check_suffix(s: &str) -> Result<(), ParseErrorKind> {
+ if s.is_empty() {
+ return Ok(());
+ }
+
+ let mut chars = s.chars();
+ let first = chars.next().unwrap();
+ let rest = chars.as_str();
+ if first == '_' && rest.is_empty() {
+ return Err(InvalidSuffix);
+ }
+
+ // This is just an extra check to improve the error message. If the first
+ // character of the "suffix" is already some invalid ASCII
+ // char, "unexpected character" seems like the more fitting error.
+ if first.is_ascii() && !(first.is_ascii_alphabetic() || first == '_') {
+ return Err(UnexpectedChar);
+ }
+
+ // Proper check is optional as it's not really necessary in proc macro
+ // context.
+ #[cfg(feature = "check_suffix")]
+ fn is_valid_suffix(first: char, rest: &str) -> bool {
+ use unicode_xid::UnicodeXID;
+
+ (first == '_' || first.is_xid_start())
+ && rest.chars().all(|c| c.is_xid_continue())
+ }
+
+ // When avoiding the dependency on `unicode_xid`, we just do a best effort
+ // to catch the most common errors.
+ #[cfg(not(feature = "check_suffix"))]
+ fn is_valid_suffix(first: char, rest: &str) -> bool {
+ if first.is_ascii() && !(first.is_ascii_alphabetic() || first == '_') {
+ return false;
+ }
+ for c in rest.chars() {
+ if c.is_ascii() && !(c.is_ascii_alphanumeric() || c == '_') {
+ return false;
+ }
+ }
+ true
+ }
+
+ if is_valid_suffix(first, rest) {
+ Ok(())
+ } else {
+ Err(InvalidSuffix)
+ }
+}
diff --git a/third_party/rust/litrs/src/string/mod.rs b/third_party/rust/litrs/src/string/mod.rs
new file mode 100644
index 0000000000..d2034a62a9
--- /dev/null
+++ b/third_party/rust/litrs/src/string/mod.rs
@@ -0,0 +1,125 @@
+use std::{fmt, ops::Range};
+
+use crate::{
+ Buffer, ParseError,
+ err::{perr, ParseErrorKind::*},
+ escape::{scan_raw_string, unescape_string},
+ parse::first_byte_or_empty,
+};
+
+
+/// A string or raw string literal, e.g. `"foo"`, `"Grüße"` or `r#"a🦊c"d🦀f"#`.
+///
+/// See [the reference][ref] for more information.
+///
+/// [ref]: https://doc.rust-lang.org/reference/tokens.html#string-literals
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct StringLit<B: Buffer> {
+ /// The raw input.
+ raw: B,
+
+ /// The string value (with all escapes unescaped), or `None` if there were
+ /// no escapes. In the latter case, the string value is in `raw`.
+ value: Option<String>,
+
+ /// The number of hash signs in case of a raw string literal, or `None` if
+ /// it's not a raw string literal.
+ num_hashes: Option<u32>,
+
+ /// Start index of the suffix or `raw.len()` if there is no suffix.
+ start_suffix: usize,
+}
+
+impl<B: Buffer> StringLit<B> {
+ /// Parses the input as a (raw) string literal. Returns an error if the
+ /// input is invalid or represents a different kind of literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ match first_byte_or_empty(&input)? {
+ b'r' | b'"' => {
+ let (value, num_hashes, start_suffix) = parse_impl(&input)?;
+ Ok(Self { raw: input, value, num_hashes, start_suffix })
+ }
+ _ => Err(perr(0, InvalidStringLiteralStart)),
+ }
+ }
+
+ /// Returns the string value this literal represents (where all escapes have
+ /// been turned into their respective values).
+ pub fn value(&self) -> &str {
+ self.value.as_deref().unwrap_or(&self.raw[self.inner_range()])
+ }
+
+ /// Like `value` but returns a potentially owned version of the value.
+ ///
+ /// The return value is either `Cow<'static, str>` if `B = String`, or
+ /// `Cow<'a, str>` if `B = &'a str`.
+ pub fn into_value(self) -> B::Cow {
+ let inner_range = self.inner_range();
+ let Self { raw, value, .. } = self;
+ value.map(B::Cow::from).unwrap_or_else(|| raw.cut(inner_range).into_cow())
+ }
+
+ /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
+ pub fn suffix(&self) -> &str {
+ &(*self.raw)[self.start_suffix..]
+ }
+
+ /// Returns whether this literal is a raw string literal (starting with
+ /// `r`).
+ pub fn is_raw_string(&self) -> bool {
+ self.num_hashes.is_some()
+ }
+
+ /// Returns the raw input that was passed to `parse`.
+ pub fn raw_input(&self) -> &str {
+ &self.raw
+ }
+
+ /// Returns the raw input that was passed to `parse`, potentially owned.
+ pub fn into_raw_input(self) -> B {
+ self.raw
+ }
+
+ /// The range within `self.raw` that excludes the quotes and potential `r#`.
+ fn inner_range(&self) -> Range<usize> {
+ match self.num_hashes {
+ None => 1..self.start_suffix - 1,
+ Some(n) => 1 + n as usize + 1..self.start_suffix - n as usize - 1,
+ }
+ }
+}
+
+impl StringLit<&str> {
+ /// Makes a copy of the underlying buffer and returns the owned version of
+ /// `Self`.
+ pub fn into_owned(self) -> StringLit<String> {
+ StringLit {
+ raw: self.raw.to_owned(),
+ value: self.value,
+ num_hashes: self.num_hashes,
+ start_suffix: self.start_suffix,
+ }
+ }
+}
+
+impl<B: Buffer> fmt::Display for StringLit<B> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad(&self.raw)
+ }
+}
+
+/// Precondition: input has to start with either `"` or `r`.
+#[inline(never)]
+pub(crate) fn parse_impl(input: &str) -> Result<(Option<String>, Option<u32>, usize), ParseError> {
+ if input.starts_with('r') {
+ scan_raw_string::<char>(&input, 1)
+ .map(|(v, hashes, start_suffix)| (v, Some(hashes), start_suffix))
+ } else {
+ unescape_string::<char>(&input, 1)
+ .map(|(v, start_suffix)| (v, None, start_suffix))
+ }
+}
+
+
+#[cfg(test)]
+mod tests;
diff --git a/third_party/rust/litrs/src/string/tests.rs b/third_party/rust/litrs/src/string/tests.rs
new file mode 100644
index 0000000000..1c0cb63061
--- /dev/null
+++ b/third_party/rust/litrs/src/string/tests.rs
@@ -0,0 +1,278 @@
+use crate::{Literal, StringLit, test_util::{assert_parse_ok_eq, assert_roundtrip}};
+
+// ===== Utility functions =======================================================================
+
+macro_rules! check {
+ ($lit:literal, $has_escapes:expr, $num_hashes:expr) => {
+ check!($lit, stringify!($lit), $has_escapes, $num_hashes, "")
+ };
+ ($lit:literal, $input:expr, $has_escapes:expr, $num_hashes:expr, $suffix:literal) => {
+ let input = $input;
+ let expected = StringLit {
+ raw: input,
+ value: if $has_escapes { Some($lit.to_string()) } else { None },
+ num_hashes: $num_hashes,
+ start_suffix: input.len() - $suffix.len(),
+ };
+
+ assert_parse_ok_eq(input, StringLit::parse(input), expected.clone(), "StringLit::parse");
+ assert_parse_ok_eq(
+ input, Literal::parse(input), Literal::String(expected.clone()), "Literal::parse");
+ let lit = StringLit::parse(input).unwrap();
+ assert_eq!(lit.value(), $lit);
+ assert_eq!(lit.suffix(), $suffix);
+ assert_eq!(lit.into_value(), $lit);
+ assert_roundtrip(expected.into_owned(), input);
+ };
+}
+
+
+// ===== Actual tests ============================================================================
+
+#[test]
+fn simple() {
+ check!("", false, None);
+ check!("a", false, None);
+ check!("peter", false, None);
+ check!("Sei gegrüßt, Bärthelt!", false, None);
+ check!("أنا لا أتحدث العربية", false, None);
+ check!("お前はもう死んでいる", false, None);
+ check!("Пушки - интересные музыкальные инструменты", false, None);
+ check!("lit 👌 😂 af", false, None);
+}
+
+#[test]
+fn special_whitespace() {
+ let strings = ["\n", "\t", "foo\tbar", "🦊\n"];
+
+ for &s in &strings {
+ let input = format!(r#""{}""#, s);
+ let input_raw = format!(r#"r"{}""#, s);
+ for (input, num_hashes) in vec![(input, None), (input_raw, Some(0))] {
+ let expected = StringLit {
+ raw: &*input,
+ value: None,
+ num_hashes,
+ start_suffix: input.len(),
+ };
+ assert_parse_ok_eq(
+ &input, StringLit::parse(&*input), expected.clone(), "StringLit::parse");
+ assert_parse_ok_eq(
+ &input, Literal::parse(&*input), Literal::String(expected), "Literal::parse");
+ assert_eq!(StringLit::parse(&*input).unwrap().value(), s);
+ assert_eq!(StringLit::parse(&*input).unwrap().into_value(), s);
+ }
+ }
+}
+
+#[test]
+fn simple_escapes() {
+ check!("a\nb", true, None);
+ check!("\nb", true, None);
+ check!("a\n", true, None);
+ check!("\n", true, None);
+
+ check!("\x60犬 \t 猫\r馬\n うさぎ \0ネズミ", true, None);
+ check!("నా \\పిల్లి లావుగా ఉంది", true, None);
+ check!("నా \\పిల్లి లావుగా 🐈\"ఉంది", true, None);
+ check!("\\నా\\ పిల్లి లావుగా\" ఉంది\"", true, None);
+ check!("\"నా \\🐈 పిల్లి లావుగా \" ఉంది\\", true, None);
+
+ check!("\x00", true, None);
+ check!(" \x01", true, None);
+ check!("\x0c 🦊", true, None);
+ check!(" 🦊\x0D ", true, None);
+ check!("\\x13", true, None);
+ check!("\"x30", true, None);
+}
+
+#[test]
+fn unicode_escapes() {
+ check!("\u{0}", true, None);
+ check!(" \u{00}", true, None);
+ check!("\u{b} ", true, None);
+ check!(" \u{B} ", true, None);
+ check!("\u{7e}", true, None);
+ check!("నక్క\u{E4}", true, None);
+ check!("\u{e4} నక్క", true, None);
+ check!(" \u{fc}నక్క ", true, None);
+ check!("\u{Fc}", true, None);
+ check!("\u{fC}🦊\nлиса", true, None);
+ check!("лиса\u{FC}", true, None);
+ check!("лиса\u{b10}నక్క🦊", true, None);
+ check!("\"నక్క\u{B10}", true, None);
+ check!("лиса\\\u{0b10}", true, None);
+ check!("ли🦊са\\\"\u{0b10}", true, None);
+ check!("నక్క\\\\u{0b10}", true, None);
+ check!("\u{2764}Füchsin", true, None);
+ check!("Füchse \u{1f602}", true, None);
+ check!("cd\u{1F602}ab", true, None);
+
+ check!("\u{0}🦊", true, None);
+ check!("лиса\u{0__}", true, None);
+ check!("\\🦊\u{3_b}", true, None);
+ check!("🦊\u{1_F_6_0_2}Füchsin", true, None);
+ check!("నక్క\\\u{1_F6_02_____}నక్క", true, None);
+}
+
+#[test]
+fn string_continue() {
+ check!("నక్క\
+ bar", true, None);
+ check!("foo\
+🦊", true, None);
+
+ check!("foo\
+
+ banana", true, None);
+
+ // Weird whitespace characters
+ let lit = StringLit::parse("\"foo\\\n\r\t\n \n\tbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), "foobar");
+ let lit = StringLit::parse("\"foo\\\n\u{85}bar\"").expect("failed to parse");
+ assert_eq!(lit.value(), "foo\u{85}bar");
+ let lit = StringLit::parse("\"foo\\\n\u{a0}bar\"").expect("failed to parse");
+ assert_eq!(lit.value(), "foo\u{a0}bar");
+
+ // Raw strings do not handle "string continues"
+ check!(r"foo\
+ bar", false, Some(0));
+}
+
+#[test]
+fn crlf_newlines() {
+ let lit = StringLit::parse("\"foo\r\nbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), "foo\nbar");
+
+ let lit = StringLit::parse("\"\r\nbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), "\nbar");
+
+ let lit = StringLit::parse("\"лиса\r\n\"").expect("failed to parse");
+ assert_eq!(lit.value(), "лиса\n");
+
+ let lit = StringLit::parse("r\"foo\r\nbar\"").expect("failed to parse");
+ assert_eq!(lit.value(), "foo\nbar");
+
+ let lit = StringLit::parse("r#\"\r\nbar\"#").expect("failed to parse");
+ assert_eq!(lit.value(), "\nbar");
+
+ let lit = StringLit::parse("r##\"лиса\r\n\"##").expect("failed to parse");
+ assert_eq!(lit.value(), "лиса\n");
+}
+
+#[test]
+fn raw_string() {
+ check!(r"", false, Some(0));
+ check!(r"a", false, Some(0));
+ check!(r"peter", false, Some(0));
+ check!(r"Sei gegrüßt, Bärthelt!", false, Some(0));
+ check!(r"أنا لا أتحدث العربية", false, Some(0));
+ check!(r"お前はもう死んでいる", false, Some(0));
+ check!(r"Пушки - интересные музыкальные инструменты", false, Some(0));
+ check!(r"lit 👌 😂 af", false, Some(0));
+
+ check!(r#""#, false, Some(1));
+ check!(r#"a"#, false, Some(1));
+ check!(r##"peter"##, false, Some(2));
+ check!(r###"Sei gegrüßt, Bärthelt!"###, false, Some(3));
+ check!(r########"lit 👌 😂 af"########, false, Some(8));
+
+ check!(r#"foo " bar"#, false, Some(1));
+ check!(r##"foo " bar"##, false, Some(2));
+ check!(r#"foo """" '"'" bar"#, false, Some(1));
+ check!(r#""foo""#, false, Some(1));
+ check!(r###""foo'"###, false, Some(3));
+ check!(r#""x'#_#s'"#, false, Some(1));
+ check!(r"#", false, Some(0));
+ check!(r"foo#", false, Some(0));
+ check!(r"##bar", false, Some(0));
+ check!(r###""##foo"##bar'"###, false, Some(3));
+
+ check!(r"さび\n\t\r\0\\x60\u{123}フェリス", false, Some(0));
+ check!(r#"さび\n\t\r\0\\x60\u{123}フェリス"#, false, Some(1));
+}
+
+#[test]
+fn suffixes() {
+ check!("hello", r###""hello"suffix"###, false, None, "suffix");
+ check!(r"お前はもう死んでいる", r###"r"お前はもう死んでいる"_banana"###, false, Some(0), "_banana");
+ check!("fox", r#""fox"peter"#, false, None, "peter");
+ check!("🦊", r#""🦊"peter"#, false, None, "peter");
+ check!("నక్క\\\\u{0b10}", r###""నక్క\\\\u{0b10}"jü_rgen"###, true, None, "jü_rgen");
+}
+
+#[test]
+fn parse_err() {
+ assert_err!(StringLit, r#"""#, UnterminatedString, None);
+ assert_err!(StringLit, r#""犬"#, UnterminatedString, None);
+ assert_err!(StringLit, r#""Jürgen"#, UnterminatedString, None);
+ assert_err!(StringLit, r#""foo bar baz"#, UnterminatedString, None);
+
+ assert_err!(StringLit, r#""fox"peter""#, InvalidSuffix, 5);
+ assert_err!(StringLit, r###"r#"foo "# bar"#"###, UnexpectedChar, 9);
+
+ assert_err!(StringLit, "\"\r\"", IsolatedCr, 1);
+ assert_err!(StringLit, "\"fo\rx\"", IsolatedCr, 3);
+ assert_err!(StringLit, "r\"\r\"", IsolatedCr, 2);
+ assert_err!(StringLit, "r\"fo\rx\"", IsolatedCr, 4);
+
+ assert_err!(StringLit, r##"r####""##, UnterminatedRawString, None);
+ assert_err!(StringLit, r#####"r##"foo"#bar"#####, UnterminatedRawString, None);
+ assert_err!(StringLit, r##"r####"##, InvalidLiteral, None);
+ assert_err!(StringLit, r##"r####x"##, InvalidLiteral, None);
+}
+
+#[test]
+fn invald_ascii_escapes() {
+ assert_err!(StringLit, r#""\x80""#, NonAsciiXEscape, 1..5);
+ assert_err!(StringLit, r#""🦊\x81""#, NonAsciiXEscape, 5..9);
+ assert_err!(StringLit, r#"" \x8a""#, NonAsciiXEscape, 2..6);
+ assert_err!(StringLit, r#""\x8Ff""#, NonAsciiXEscape, 1..5);
+ assert_err!(StringLit, r#""\xa0 ""#, NonAsciiXEscape, 1..5);
+ assert_err!(StringLit, r#""నక్క\xB0""#, NonAsciiXEscape, 13..17);
+ assert_err!(StringLit, r#""\xc3నక్క""#, NonAsciiXEscape, 1..5);
+ assert_err!(StringLit, r#""\xDf🦊""#, NonAsciiXEscape, 1..5);
+ assert_err!(StringLit, r#""నక్క\xffనక్క""#, NonAsciiXEscape, 13..17);
+ assert_err!(StringLit, r#""\xfF ""#, NonAsciiXEscape, 1..5);
+ assert_err!(StringLit, r#"" \xFf""#, NonAsciiXEscape, 2..6);
+ assert_err!(StringLit, r#""నక్క \xFF""#, NonAsciiXEscape, 15..19);
+}
+
+#[test]
+fn invalid_escapes() {
+ assert_err!(StringLit, r#""\a""#, UnknownEscape, 1..3);
+ assert_err!(StringLit, r#""foo\y""#, UnknownEscape, 4..6);
+ assert_err!(StringLit, r#""\"#, UnterminatedEscape, 1);
+ assert_err!(StringLit, r#""\x""#, UnterminatedEscape, 1..3);
+ assert_err!(StringLit, r#""🦊\x1""#, UnterminatedEscape, 5..8);
+ assert_err!(StringLit, r#"" \xaj""#, InvalidXEscape, 2..6);
+ assert_err!(StringLit, r#""నక్క\xjb""#, InvalidXEscape, 13..17);
+}
+
+#[test]
+fn invalid_unicode_escapes() {
+ assert_err!(StringLit, r#""\u""#, UnicodeEscapeWithoutBrace, 1..3);
+ assert_err!(StringLit, r#""🦊\u ""#, UnicodeEscapeWithoutBrace, 5..7);
+ assert_err!(StringLit, r#""\u3""#, UnicodeEscapeWithoutBrace, 1..3);
+
+ assert_err!(StringLit, r#""\u{""#, UnterminatedUnicodeEscape, 1..4);
+ assert_err!(StringLit, r#""\u{12""#, UnterminatedUnicodeEscape, 1..6);
+ assert_err!(StringLit, r#""🦊\u{a0b""#, UnterminatedUnicodeEscape, 5..11);
+ assert_err!(StringLit, r#""\u{a0_b ""#, UnterminatedUnicodeEscape, 1..10);
+
+ assert_err!(StringLit, r#""\u{_}నక్క""#, InvalidStartOfUnicodeEscape, 4);
+ assert_err!(StringLit, r#""\u{_5f}""#, InvalidStartOfUnicodeEscape, 4);
+
+ assert_err!(StringLit, r#""fox\u{x}""#, NonHexDigitInUnicodeEscape, 7);
+ assert_err!(StringLit, r#""\u{0x}🦊""#, NonHexDigitInUnicodeEscape, 5);
+ assert_err!(StringLit, r#""నక్క\u{3bx}""#, NonHexDigitInUnicodeEscape, 18);
+ assert_err!(StringLit, r#""\u{3b_x}лиса""#, NonHexDigitInUnicodeEscape, 7);
+ assert_err!(StringLit, r#""\u{4x_}""#, NonHexDigitInUnicodeEscape, 5);
+
+ assert_err!(StringLit, r#""\u{1234567}""#, TooManyDigitInUnicodeEscape, 10);
+ assert_err!(StringLit, r#""నక్క\u{1234567}🦊""#, TooManyDigitInUnicodeEscape, 22);
+ assert_err!(StringLit, r#""నక్క\u{1_23_4_56_7}""#, TooManyDigitInUnicodeEscape, 26);
+ assert_err!(StringLit, r#""\u{abcdef123}лиса""#, TooManyDigitInUnicodeEscape, 10);
+
+ assert_err!(StringLit, r#""\u{110000}fox""#, InvalidUnicodeEscapeChar, 1..10);
+}
diff --git a/third_party/rust/litrs/src/test_util.rs b/third_party/rust/litrs/src/test_util.rs
new file mode 100644
index 0000000000..fd284e984e
--- /dev/null
+++ b/third_party/rust/litrs/src/test_util.rs
@@ -0,0 +1,128 @@
+use crate::*;
+use std::fmt::{Debug, Display};
+
+
+#[track_caller]
+pub(crate) fn assert_parse_ok_eq<T: PartialEq + Debug + Display>(
+ input: &str,
+ result: Result<T, ParseError>,
+ expected: T,
+ parse_method: &str,
+) {
+ match result {
+ Ok(actual) if actual == expected => {
+ if actual.to_string() != input {
+ panic!(
+ "formatting does not yield original input `{}`: {:?}",
+ input,
+ actual,
+ );
+ }
+ }
+ Ok(actual) => {
+ panic!(
+ "unexpected parsing result (with `{}`) for `{}`:\nactual: {:?}\nexpected: {:?}",
+ parse_method,
+ input,
+ actual,
+ expected,
+ );
+ }
+ Err(e) => {
+ panic!(
+ "expected `{}` to be parsed (with `{}`) successfully, but it failed: {:?}",
+ input,
+ parse_method,
+ e,
+ );
+ }
+ }
+}
+
+// This is not ideal, but to perform this check we need `proc-macro2`. So we
+// just don't do anything if that feature is not enabled.
+#[cfg(not(feature = "proc-macro2"))]
+pub(crate) fn assert_roundtrip<T>(_: T, _: &str) {}
+
+#[cfg(feature = "proc-macro2")]
+#[track_caller]
+pub(crate) fn assert_roundtrip<T>(ours: T, input: &str)
+where
+ T: std::convert::TryFrom<proc_macro2::Literal> + fmt::Debug + PartialEq + Clone,
+ proc_macro2::Literal: From<T>,
+ <T as std::convert::TryFrom<proc_macro2::Literal>>::Error: std::fmt::Display,
+{
+ let pm_lit = input.parse::<proc_macro2::Literal>()
+ .expect("failed to parse input as proc_macro2::Literal");
+ let t_name = std::any::type_name::<T>();
+
+ // Unfortunately, `proc_macro2::Literal` does not implement `PartialEq`, so
+ // this is the next best thing.
+ if proc_macro2::Literal::from(ours.clone()).to_string() != pm_lit.to_string() {
+ panic!(
+ "Converting {} to proc_macro2::Literal has unexpected result:\
+ \nconverted: {:?}\nexpected: {:?}",
+ t_name,
+ proc_macro2::Literal::from(ours),
+ pm_lit,
+ );
+ }
+
+ match T::try_from(pm_lit) {
+ Err(e) => {
+ panic!("Trying to convert proc_macro2::Literal to {} results in error: {}", t_name, e);
+ }
+ Ok(res) => {
+ if res != ours {
+ panic!(
+ "Converting proc_macro2::Literal to {} has unexpected result:\
+ \nactual: {:?}\nexpected: {:?}",
+ t_name,
+ res,
+ ours,
+ );
+ }
+ }
+ }
+}
+
+macro_rules! assert_err {
+ ($ty:ident, $input:literal, $kind:ident, $( $span:tt )+ ) => {
+ assert_err_single!($ty::parse($input), $kind, $($span)+);
+ assert_err_single!($crate::Literal::parse($input), $kind, $($span)+);
+ };
+}
+
+macro_rules! assert_err_single {
+ ($expr:expr, $kind:ident, $( $span:tt )+ ) => {
+ let res = $expr;
+ let err = match res {
+ Err(e) => e,
+ Ok(v) => panic!(
+ "Expected `{}` to return an error, but it returned Ok({:?})",
+ stringify!($expr),
+ v,
+ ),
+ };
+ if err.kind != $crate::err::ParseErrorKind::$kind {
+ panic!(
+ "Expected error kind {} for `{}` but got {:?}",
+ stringify!($kind),
+ stringify!($expr),
+ err.kind,
+ )
+ }
+ let expected_span = assert_err_single!(@span $($span)+);
+ if err.span != expected_span {
+ panic!(
+ "Expected error span {:?} for `{}` but got {:?}",
+ expected_span,
+ stringify!($expr),
+ err.span,
+ )
+ }
+ };
+ (@span $start:literal .. $end:literal) => { Some($start .. $end) };
+ (@span $at:literal) => { Some($at.. $at + 1) };
+ (@span None) => { None };
+}
diff --git a/third_party/rust/litrs/src/tests.rs b/third_party/rust/litrs/src/tests.rs
new file mode 100644
index 0000000000..613b429540
--- /dev/null
+++ b/third_party/rust/litrs/src/tests.rs
@@ -0,0 +1,349 @@
+use crate::Literal;
+
+
+#[test]
+fn empty() {
+ assert_err!(Literal, "", Empty, None);
+}
+
+#[test]
+fn invalid_literals() {
+ assert_err_single!(Literal::parse("."), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("+"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("-"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("e"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("e8"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("f32"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("foo"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("inf"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("nan"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("NaN"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("NAN"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("_2.7"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse(".5"), InvalidLiteral, None);
+}
+
+#[test]
+fn misc() {
+ assert_err_single!(Literal::parse("0x44.5"), UnexpectedChar, 4..6);
+ assert_err_single!(Literal::parse("a"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse(";"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("0;"), UnexpectedChar, 1);
+ assert_err_single!(Literal::parse(" 0"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("0 "), UnexpectedChar, 1);
+ assert_err_single!(Literal::parse("_"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("_3"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("a_123"), InvalidLiteral, None);
+ assert_err_single!(Literal::parse("B_123"), InvalidLiteral, None);
+}
+
+macro_rules! assert_no_panic {
+ ($input:expr) => {
+ let arr = $input;
+ let input = std::str::from_utf8(&arr).expect("not unicode");
+ let res = std::panic::catch_unwind(move || {
+ let _ = Literal::parse(input);
+ let _ = crate::BoolLit::parse(input);
+ let _ = crate::IntegerLit::parse(input);
+ let _ = crate::FloatLit::parse(input);
+ let _ = crate::CharLit::parse(input);
+ let _ = crate::StringLit::parse(input);
+ let _ = crate::ByteLit::parse(input);
+ let _ = crate::ByteStringLit::parse(input);
+ });
+
+ if let Err(e) = res {
+ println!("\n!!! panic for: {:?}", input);
+ std::panic::resume_unwind(e);
+ }
+ };
+}
+
+#[test]
+#[ignore]
+fn never_panic_up_to_3() {
+ for a in 0..128 {
+ assert_no_panic!([a]);
+ for b in 0..128 {
+ assert_no_panic!([a, b]);
+ for c in 0..128 {
+ assert_no_panic!([a, b, c]);
+ }
+ }
+ }
+}
+
+// This test takes super long in debug mode, but in release mode it's fine.
+#[test]
+#[ignore]
+fn never_panic_len_4() {
+ for a in 0..128 {
+ for b in 0..128 {
+ for c in 0..128 {
+ for d in 0..128 {
+ assert_no_panic!([a, b, c, d]);
+ }
+ }
+ }
+ }
+}
+
+#[cfg(feature = "proc-macro2")]
+#[test]
+fn proc_macro() {
+ use std::convert::TryFrom;
+ use proc_macro2::{
+ self as pm2, TokenTree, Group, TokenStream, Delimiter, Spacing, Punct, Span, Ident,
+ };
+ use crate::{
+ BoolLit, ByteLit, ByteStringLit, CharLit, FloatLit, IntegerLit, StringLit, err::TokenKind
+ };
+
+
+ macro_rules! assert_invalid_token {
+ ($input:expr, expected: $expected:path, actual: $actual:path $(,)?) => {
+ let err = $input.unwrap_err();
+ if err.expected != $expected {
+ panic!(
+ "err.expected was expected to be {:?}, but is {:?}",
+ $expected,
+ err.expected,
+ );
+ }
+ if err.actual != $actual {
+ panic!("err.actual was expected to be {:?}, but is {:?}", $actual, err.actual);
+ }
+ };
+ }
+
+
+ let pm_u16_lit = pm2::Literal::u16_suffixed(2700);
+ let pm_i16_lit = pm2::Literal::i16_unsuffixed(3912);
+ let pm_f32_lit = pm2::Literal::f32_unsuffixed(3.14);
+ let pm_f64_lit = pm2::Literal::f64_suffixed(99.3);
+ let pm_string_lit = pm2::Literal::string("hello 🦊");
+ let pm_bytestr_lit = pm2::Literal::byte_string(b"hello \nfoxxo");
+ let pm_char_lit = pm2::Literal::character('🦀');
+
+ let u16_lit = Literal::parse("2700u16".to_string()).unwrap();
+ let i16_lit = Literal::parse("3912".to_string()).unwrap();
+ let f32_lit = Literal::parse("3.14".to_string()).unwrap();
+ let f64_lit = Literal::parse("99.3f64".to_string()).unwrap();
+ let string_lit = Literal::parse(r#""hello 🦊""#.to_string()).unwrap();
+ let bytestr_lit = Literal::parse(r#"b"hello \nfoxxo""#.to_string()).unwrap();
+ let char_lit = Literal::parse("'🦀'".to_string()).unwrap();
+
+ assert_eq!(Literal::from(&pm_u16_lit), u16_lit);
+ assert_eq!(Literal::from(&pm_i16_lit), i16_lit);
+ assert_eq!(Literal::from(&pm_f32_lit), f32_lit);
+ assert_eq!(Literal::from(&pm_f64_lit), f64_lit);
+ assert_eq!(Literal::from(&pm_string_lit), string_lit);
+ assert_eq!(Literal::from(&pm_bytestr_lit), bytestr_lit);
+ assert_eq!(Literal::from(&pm_char_lit), char_lit);
+
+
+ let group = TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()));
+ let punct = TokenTree::from(Punct::new(':', Spacing::Alone));
+ let ident = TokenTree::from(Ident::new("peter", Span::call_site()));
+
+ assert_eq!(
+ Literal::try_from(TokenTree::Literal(pm2::Literal::string("hello 🦊"))).unwrap(),
+ Literal::String(StringLit::parse(r#""hello 🦊""#.to_string()).unwrap()),
+ );
+ assert_invalid_token!(
+ Literal::try_from(punct.clone()),
+ expected: TokenKind::Literal,
+ actual: TokenKind::Punct,
+ );
+ assert_invalid_token!(
+ Literal::try_from(group.clone()),
+ expected: TokenKind::Literal,
+ actual: TokenKind::Group,
+ );
+ assert_invalid_token!(
+ Literal::try_from(ident.clone()),
+ expected: TokenKind::Literal,
+ actual: TokenKind::Ident,
+ );
+
+
+ assert_eq!(Literal::from(IntegerLit::try_from(pm_u16_lit.clone()).unwrap()), u16_lit);
+ assert_eq!(Literal::from(IntegerLit::try_from(pm_i16_lit.clone()).unwrap()), i16_lit);
+ assert_eq!(Literal::from(FloatLit::try_from(pm_f32_lit.clone()).unwrap()), f32_lit);
+ assert_eq!(Literal::from(FloatLit::try_from(pm_f64_lit.clone()).unwrap()), f64_lit);
+ assert_eq!(Literal::from(StringLit::try_from(pm_string_lit.clone()).unwrap()), string_lit);
+ assert_eq!(
+ Literal::from(ByteStringLit::try_from(pm_bytestr_lit.clone()).unwrap()),
+ bytestr_lit,
+ );
+ assert_eq!(Literal::from(CharLit::try_from(pm_char_lit.clone()).unwrap()), char_lit);
+
+ assert_invalid_token!(
+ StringLit::try_from(pm_u16_lit.clone()),
+ expected: TokenKind::StringLit,
+ actual: TokenKind::IntegerLit,
+ );
+ assert_invalid_token!(
+ StringLit::try_from(pm_f32_lit.clone()),
+ expected: TokenKind::StringLit,
+ actual: TokenKind::FloatLit,
+ );
+ assert_invalid_token!(
+ ByteLit::try_from(pm_bytestr_lit.clone()),
+ expected: TokenKind::ByteLit,
+ actual: TokenKind::ByteStringLit,
+ );
+ assert_invalid_token!(
+ ByteLit::try_from(pm_i16_lit.clone()),
+ expected: TokenKind::ByteLit,
+ actual: TokenKind::IntegerLit,
+ );
+ assert_invalid_token!(
+ IntegerLit::try_from(pm_string_lit.clone()),
+ expected: TokenKind::IntegerLit,
+ actual: TokenKind::StringLit,
+ );
+ assert_invalid_token!(
+ IntegerLit::try_from(pm_char_lit.clone()),
+ expected: TokenKind::IntegerLit,
+ actual: TokenKind::CharLit,
+ );
+
+
+ assert_eq!(
+ Literal::from(IntegerLit::try_from(TokenTree::from(pm_u16_lit.clone())).unwrap()),
+ u16_lit,
+ );
+ assert_eq!(
+ Literal::from(IntegerLit::try_from(TokenTree::from(pm_i16_lit.clone())).unwrap()),
+ i16_lit,
+ );
+ assert_eq!(
+ Literal::from(FloatLit::try_from(TokenTree::from(pm_f32_lit.clone())).unwrap()),
+ f32_lit,
+ );
+ assert_eq!(
+ Literal::from(FloatLit::try_from(TokenTree::from(pm_f64_lit.clone())).unwrap()),
+ f64_lit,
+ );
+ assert_eq!(
+ Literal::from(StringLit::try_from(TokenTree::from(pm_string_lit.clone())).unwrap()),
+ string_lit,
+ );
+ assert_eq!(
+ Literal::from(ByteStringLit::try_from(TokenTree::from(pm_bytestr_lit.clone())).unwrap()),
+ bytestr_lit,
+ );
+ assert_eq!(
+ Literal::from(CharLit::try_from(TokenTree::from(pm_char_lit.clone())).unwrap()),
+ char_lit,
+ );
+
+ assert_invalid_token!(
+ StringLit::try_from(TokenTree::from(pm_u16_lit.clone())),
+ expected: TokenKind::StringLit,
+ actual: TokenKind::IntegerLit,
+ );
+ assert_invalid_token!(
+ StringLit::try_from(TokenTree::from(pm_f32_lit.clone())),
+ expected: TokenKind::StringLit,
+ actual: TokenKind::FloatLit,
+ );
+ assert_invalid_token!(
+ BoolLit::try_from(TokenTree::from(pm_bytestr_lit.clone())),
+ expected: TokenKind::BoolLit,
+ actual: TokenKind::ByteStringLit,
+ );
+ assert_invalid_token!(
+ BoolLit::try_from(TokenTree::from(pm_i16_lit.clone())),
+ expected: TokenKind::BoolLit,
+ actual: TokenKind::IntegerLit,
+ );
+ assert_invalid_token!(
+ IntegerLit::try_from(TokenTree::from(pm_string_lit.clone())),
+ expected: TokenKind::IntegerLit,
+ actual: TokenKind::StringLit,
+ );
+ assert_invalid_token!(
+ IntegerLit::try_from(TokenTree::from(pm_char_lit.clone())),
+ expected: TokenKind::IntegerLit,
+ actual: TokenKind::CharLit,
+ );
+
+ assert_invalid_token!(
+ StringLit::try_from(TokenTree::from(group)),
+ expected: TokenKind::StringLit,
+ actual: TokenKind::Group,
+ );
+ assert_invalid_token!(
+ BoolLit::try_from(TokenTree::from(punct)),
+ expected: TokenKind::BoolLit,
+ actual: TokenKind::Punct,
+ );
+ assert_invalid_token!(
+ FloatLit::try_from(TokenTree::from(ident)),
+ expected: TokenKind::FloatLit,
+ actual: TokenKind::Ident,
+ );
+}
+
+#[cfg(feature = "proc-macro2")]
+#[test]
+fn bool_try_from_tt() {
+ use std::convert::TryFrom;
+ use proc_macro2::{Ident, Span, TokenTree};
+ use crate::BoolLit;
+
+
+ let ident = |s: &str| Ident::new(s, Span::call_site());
+
+ assert_eq!(BoolLit::try_from(TokenTree::Ident(ident("true"))).unwrap(), BoolLit::True);
+ assert_eq!(BoolLit::try_from(TokenTree::Ident(ident("false"))).unwrap(), BoolLit::False);
+
+ assert!(BoolLit::try_from(TokenTree::Ident(ident("falsex"))).is_err());
+ assert!(BoolLit::try_from(TokenTree::Ident(ident("_false"))).is_err());
+ assert!(BoolLit::try_from(TokenTree::Ident(ident("False"))).is_err());
+ assert!(BoolLit::try_from(TokenTree::Ident(ident("True"))).is_err());
+ assert!(BoolLit::try_from(TokenTree::Ident(ident("ltrue"))).is_err());
+
+
+ assert_eq!(
+ Literal::try_from(TokenTree::Ident(ident("true"))).unwrap(),
+ Literal::Bool(BoolLit::True),
+ );
+ assert_eq!(
+ Literal::try_from(TokenTree::Ident(ident("false"))).unwrap(),
+ Literal::Bool(BoolLit::False),
+ );
+
+ assert!(Literal::try_from(TokenTree::Ident(ident("falsex"))).is_err());
+ assert!(Literal::try_from(TokenTree::Ident(ident("_false"))).is_err());
+ assert!(Literal::try_from(TokenTree::Ident(ident("False"))).is_err());
+ assert!(Literal::try_from(TokenTree::Ident(ident("True"))).is_err());
+ assert!(Literal::try_from(TokenTree::Ident(ident("ltrue"))).is_err());
+}
+
+#[cfg(feature = "proc-macro2")]
+#[test]
+fn invalid_token_display() {
+ use crate::{InvalidToken, err::TokenKind};
+
+ let span = crate::err::Span::Two(proc_macro2::Span::call_site());
+ assert_eq!(
+ InvalidToken {
+ actual: TokenKind::StringLit,
+ expected: TokenKind::FloatLit,
+ span,
+ }.to_string(),
+ r#"expected a float literal (e.g. `3.14`), but found a string literal (e.g. "Ferris")"#,
+ );
+
+ assert_eq!(
+ InvalidToken {
+ actual: TokenKind::Punct,
+ expected: TokenKind::Literal,
+ span,
+ }.to_string(),
+ r#"expected a literal, but found a punctuation character"#,
+ );
+}