const-str-0.7.0/.cargo_vcs_info.json0000644000000001470000000000100127760ustar { "git": { "sha1": "7a49d855603767d8b48a087adfb0e4b56bebf0bb" }, "path_in_vcs": "const-str" }const-str-0.7.0/Cargo.lock0000644000000067430000000000100107610ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "const-str" version = "0.7.0" dependencies = [ "const-str-proc-macro", "heck", "http", "regex", ] [[package]] name = "const-str-proc-macro" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08a8aee16926ee1c4ad18868b8c3dfe5106359053f91e035861ec2a17116988" dependencies = [ "heck", "http", "proc-macro2", "quote", "regex", "syn", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" const-str-0.7.0/Cargo.toml0000644000000031240000000000100107720ustar # 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 = "2021" rust-version = "1.77.0" name = "const-str" version = "0.7.0" authors = ["Nugine "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "compile-time string operations" readme = "README.md" keywords = [ "string", "const", "proc-macro", ] categories = [ "text-processing", "no-std", ] license = "MIT" repository = "https://github.com/Nugine/const-str" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [features] all = [ "std", "proc", "regex", "http", "case", ] case = [ "proc", "const-str-proc-macro?/heck", ] default = [] http = [ "proc", "const-str-proc-macro?/http", ] proc = ["dep:const-str-proc-macro"] regex = [ "proc", "const-str-proc-macro?/regex", ] std = [] unstable = [] [lib] name = "const_str" path = "src/lib.rs" [dependencies.const-str-proc-macro] version = "0.7.0" optional = true [dev-dependencies.heck] version = "0.5.0" [dev-dependencies.http] version = "1.0.0" [dev-dependencies.regex] version = "1.7.0" const-str-0.7.0/Cargo.toml.orig000064400000000000000000000015601046102023000144550ustar 00000000000000[package] name = "const-str" version = "0.7.0" authors = ["Nugine "] edition = "2021" description = "compile-time string operations" license = "MIT" repository = "https://github.com/Nugine/const-str" keywords = ["string", "const", "proc-macro"] categories = ["text-processing", "no-std"] readme = "../README.md" rust-version = "1.77.0" [features] default = [] std = [] proc = ["dep:const-str-proc-macro"] regex = ["proc", "const-str-proc-macro?/regex"] http = ["proc", "const-str-proc-macro?/http"] case = ["proc", "const-str-proc-macro?/heck"] all = ["std", "proc", "regex", "http", "case"] unstable = [] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies.const-str-proc-macro] version = "0.7.0" path = "../const-str-proc-macro" optional = true [dev-dependencies] regex = "1.7.0" http = "1.0.0" heck = "0.5.0" const-str-0.7.0/LICENSE000064400000000000000000000020541046102023000125720ustar 00000000000000MIT License Copyright (c) 2020-2025 Nugine 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. const-str-0.7.0/README.md000064400000000000000000000014131046102023000130420ustar 00000000000000# const-str [![Crates.io][crates-badge]][crates-url] [![MIT licensed][mit-badge]][mit-url] [![Docs][docs-badge]][docs-url] [![Downloads]][downloads] [crates-badge]: https://img.shields.io/crates/v/const-str.svg [crates-url]: https://crates.io/crates/const-str [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: ./LICENSE [docs-badge]: https://docs.rs/const-str/badge.svg [docs-url]: https://docs.rs/const-str/ [downloads]: https://img.shields.io/crates/d/const-str Compile-time string operations Documentation: ## Contributing + [Development Guide](./CONTRIBUTING.md) ## Sponsor If my open-source work has been helpful to you, please [sponsor me](https://github.com/Nugine#sponsor). Every little bit helps. Thank you! const-str-0.7.0/src/__ctfe/ascii_case.rs000064400000000000000000000436311046102023000162320ustar 00000000000000#![allow(unsafe_code)] use core::ops::Range; use crate::__ctfe::StrBuf; use crate::slice::subslice; #[derive(Clone, Copy)] #[repr(u8)] enum TokenKind { NonAscii = 1, Lower = 2, Upper = 3, Digit = 4, Dot = 5, Other = 6, } impl TokenKind { const fn new(b: u8) -> Self { if !b.is_ascii() { return TokenKind::NonAscii; } if b.is_ascii_lowercase() { return TokenKind::Lower; } if b.is_ascii_uppercase() { return TokenKind::Upper; } if b.is_ascii_digit() { return TokenKind::Digit; } if b == b'.' { return TokenKind::Dot; } TokenKind::Other } const fn is_boundary_word(s: &[u8]) -> bool { let mut i = 0; while i < s.len() { let kind = Self::new(s[i]); match kind { TokenKind::Other | TokenKind::Dot => {} _ => return false, } i += 1; } true } } #[derive(Debug)] struct Boundaries { buf: [usize; N], len: usize, } impl Boundaries { const fn new(src: &str) -> Self { let s = src.as_bytes(); assert!(s.len() + 1 == N); let mut buf = [0; N]; let mut pos = 0; macro_rules! push { ($x: expr) => {{ buf[pos] = $x; pos += 1; }}; } let mut k2: Option = None; let mut k1: Option = None; let mut i = 0; while i < s.len() { let b = s[i]; let k0 = TokenKind::new(b); use TokenKind::*; match (k1, k0) { (None, _) => push!(i), (Some(k1), k0) => { if k1 as u8 != k0 as u8 { match (k1, k0) { (Upper, Lower) => push!(i - 1), (NonAscii, Digit) => {} // Don't create boundary between NonAscii and Digit (NonAscii, Lower | Upper) => {} // Don't create boundary between NonAscii and alphabetic (Lower | Upper, Digit) => {} // or-pattens stable since 1.53 (Digit, Lower | Upper | NonAscii) => {} (Lower | Upper, NonAscii) => {} // Don't create boundary between alphabetic and NonAscii (_, Dot) => {} (Dot, _) => match (k2, k0) { (None, _) => push!(i), (Some(_), _) => { push!(i - 1); push!(i); } }, _ => push!(i), } } } } k2 = k1; k1 = Some(k0); i += 1; } push!(i); Self { buf, len: pos } } const fn words_count(&self) -> usize { self.len - 1 } const fn word_range(&self, idx: usize) -> Range { self.buf[idx]..self.buf[idx + 1] } } pub enum AsciiCase { Lower, Upper, LowerCamel, UpperCamel, Title, Train, Snake, Kebab, ShoutySnake, ShoutyKebab, } impl AsciiCase { const fn get_seperator(&self) -> Option { match self { Self::Title => Some(b' '), Self::Snake | Self::ShoutySnake => Some(b'_'), Self::Train | Self::Kebab | Self::ShoutyKebab => Some(b'-'), _ => None, } } } pub struct ConvAsciiCase(pub T, pub AsciiCase); impl ConvAsciiCase<&str> { pub const fn output_len(&self) -> usize { assert!(self.0.len() + 1 == M); use AsciiCase::*; match self.1 { Lower | Upper => self.0.len(), LowerCamel | UpperCamel | Title | Train | Snake | Kebab | ShoutySnake | ShoutyKebab => { let mut ans = 0; let has_sep = self.1.get_seperator().is_some(); let boundaries = Boundaries::::new(self.0); let words_count = boundaries.words_count(); let mut i = 0; let mut is_starting_boundary: bool = true; while i < words_count { let rng = boundaries.word_range(i); let word = subslice(self.0.as_bytes(), rng); if !TokenKind::is_boundary_word(word) { if has_sep && !is_starting_boundary { ans += 1; } ans += word.len(); is_starting_boundary = false; } i += 1; } ans } } } pub const fn const_eval(&self) -> StrBuf { assert!(self.0.len() + 1 == M); let mut buf = [0; N]; let mut pos = 0; let s = self.0.as_bytes(); macro_rules! push { ($x: expr) => {{ buf[pos] = $x; pos += 1; }}; } use AsciiCase::*; match self.1 { Lower => { while pos < s.len() { push!(s[pos].to_ascii_lowercase()); } } Upper => { while pos < s.len() { push!(s[pos].to_ascii_uppercase()); } } LowerCamel | UpperCamel | Title | Train | Snake | Kebab | ShoutySnake | ShoutyKebab => { let sep = self.1.get_seperator(); let boundaries = Boundaries::::new(self.0); let words_count = boundaries.words_count(); let mut i = 0; let mut is_starting_boundary = true; while i < words_count { let rng = boundaries.word_range(i); let word = subslice(self.0.as_bytes(), rng); if !TokenKind::is_boundary_word(word) { if let (Some(sep), false) = (sep, is_starting_boundary) { push!(sep) } let mut j = 0; while j < word.len() { let b = match self.1 { Snake | Kebab => word[j].to_ascii_lowercase(), ShoutySnake | ShoutyKebab => word[j].to_ascii_uppercase(), LowerCamel | UpperCamel | Title | Train => { let is_upper = match self.1 { LowerCamel => !is_starting_boundary && j == 0, UpperCamel | Title | Train => j == 0, _ => unreachable!(), }; if is_upper { word[j].to_ascii_uppercase() } else { word[j].to_ascii_lowercase() } } _ => unreachable!(), }; push!(b); j += 1; } is_starting_boundary = false; } i += 1; } } } assert!(pos == N); unsafe { StrBuf::new_unchecked(buf) } } } #[doc(hidden)] #[macro_export] macro_rules! __conv_ascii_case { ($s: expr, $case: expr) => {{ const INPUT: &str = $s; const M: usize = INPUT.len() + 1; const N: usize = $crate::__ctfe::ConvAsciiCase(INPUT, $case).output_len::(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::ConvAsciiCase(INPUT, $case).const_eval::(); OUTPUT_BUF.as_str() }}; } /// Converts a string slice to a specified case. Non-ascii characters are not affected. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ``` /// use const_str::convert_ascii_case; /// /// const S1: &str = convert_ascii_case!(lower, "Lower Case"); /// const S2: &str = convert_ascii_case!(upper, "Upper Case"); /// const S3: &str = convert_ascii_case!(lower_camel, "lower camel case"); /// const S4: &str = convert_ascii_case!(upper_camel, "upper camel case"); /// const S5: &str = convert_ascii_case!(title, "title case"); /// const S6: &str = convert_ascii_case!(train, "train case"); /// const S7: &str = convert_ascii_case!(snake, "snake case"); /// const S8: &str = convert_ascii_case!(kebab, "kebab case"); /// const S9: &str = convert_ascii_case!(shouty_snake, "shouty snake case"); /// const S10: &str = convert_ascii_case!(shouty_kebab, "shouty kebab case"); /// /// assert_eq!(S1, "lower case"); /// assert_eq!(S2, "UPPER CASE"); /// assert_eq!(S3, "lowerCamelCase"); /// assert_eq!(S4, "UpperCamelCase"); /// assert_eq!(S5, "Title Case"); /// assert_eq!(S6, "Train-Case"); /// assert_eq!(S7, "snake_case"); /// assert_eq!(S8, "kebab-case"); /// assert_eq!(S9, "SHOUTY_SNAKE_CASE"); /// assert_eq!(S10, "SHOUTY-KEBAB-CASE"); /// ``` #[macro_export] macro_rules! convert_ascii_case { (lower, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::Lower) }; (upper, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::Upper) }; (lower_camel, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::LowerCamel) }; (upper_camel, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::UpperCamel) }; (title, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::Title) }; (train, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::Train) }; (snake, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::Snake) }; (kebab, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::Kebab) }; (shouty_snake, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::ShoutySnake) }; (shouty_kebab, $s: expr) => { $crate::__conv_ascii_case!($s, $crate::__ctfe::AsciiCase::ShoutyKebab) }; } #[cfg(test)] mod tests { #[test] fn test_conv_ascii_case() { macro_rules! test_conv_ascii_case { ($v: tt, $a: expr, $b: expr $(,)?) => {{ const A: &str = $a; const B: &str = convert_ascii_case!($v, A); assert_eq!(B, $b); test_conv_ascii_case!(heck, $v, $a, $b); }}; (heck, assert_eq, $c: expr, $b: expr) => {{ if $c != $b { println!("heck mismatch:\nheck: {:?}\nexpected: {:?}\n", $c, $b); } }}; (heck, lower_camel, $a: expr, $b: expr) => {{ use heck::ToLowerCamelCase; let c: String = $a.to_lower_camel_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, upper_camel, $a: expr, $b: expr) => {{ use heck::ToUpperCamelCase; let c: String = $a.to_upper_camel_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, title, $a: expr, $b: expr) => {{ use heck::ToTitleCase; let c: String = $a.to_title_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, train, $a: expr, $b: expr) => {{ use heck::ToTrainCase; let c: String = $a.to_train_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, snake, $a: expr, $b: expr) => {{ use heck::ToSnakeCase; let c: String = $a.to_snake_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, kebab, $a: expr, $b: expr) => {{ use heck::ToKebabCase; let c: String = $a.to_kebab_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, shouty_snake, $a: expr, $b: expr) => {{ use heck::ToShoutySnakeCase; let c: String = $a.to_shouty_snake_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; (heck, shouty_kebab, $a: expr, $b: expr) => {{ use heck::ToShoutyKebabCase; let c: String = $a.to_shouty_kebab_case(); test_conv_ascii_case!(heck, assert_eq, c.as_str(), $b); }}; } { const S: &str = "b.8"; test_conv_ascii_case!(lower_camel, S, "b8"); test_conv_ascii_case!(upper_camel, S, "B8"); test_conv_ascii_case!(title, S, "B 8"); test_conv_ascii_case!(train, S, "B-8"); test_conv_ascii_case!(snake, S, "b_8"); test_conv_ascii_case!(kebab, S, "b-8"); test_conv_ascii_case!(shouty_snake, S, "B_8"); test_conv_ascii_case!(shouty_kebab, S, "B-8"); } { const S: &str = "Hello World123!XMLHttp我4t5.c6.7b.8"; test_conv_ascii_case!(lower_camel, S, "helloWorld123XmlHttp我4t5C67b8"); test_conv_ascii_case!(upper_camel, S, "HelloWorld123XmlHttp我4t5C67b8"); test_conv_ascii_case!(title, S, "Hello World123 Xml Http我4t5 C6 7b 8"); test_conv_ascii_case!(train, S, "Hello-World123-Xml-Http我4t5-C6-7b-8"); test_conv_ascii_case!(snake, S, "hello_world123_xml_http我4t5_c6_7b_8"); test_conv_ascii_case!(kebab, S, "hello-world123-xml-http我4t5-c6-7b-8"); test_conv_ascii_case!(shouty_snake, S, "HELLO_WORLD123_XML_HTTP我4T5_C6_7B_8"); test_conv_ascii_case!(shouty_kebab, S, "HELLO-WORLD123-XML-HTTP我4T5-C6-7B-8"); } { const S: &str = "XMLHttpRequest"; test_conv_ascii_case!(lower_camel, S, "xmlHttpRequest"); test_conv_ascii_case!(upper_camel, S, "XmlHttpRequest"); test_conv_ascii_case!(title, S, "Xml Http Request"); test_conv_ascii_case!(train, S, "Xml-Http-Request"); test_conv_ascii_case!(snake, S, "xml_http_request"); test_conv_ascii_case!(kebab, S, "xml-http-request"); test_conv_ascii_case!(shouty_snake, S, "XML_HTTP_REQUEST"); test_conv_ascii_case!(shouty_kebab, S, "XML-HTTP-REQUEST"); } { const S: &str = " hello world "; test_conv_ascii_case!(lower_camel, S, "helloWorld"); test_conv_ascii_case!(upper_camel, S, "HelloWorld"); test_conv_ascii_case!(title, S, "Hello World"); test_conv_ascii_case!(train, S, "Hello-World"); test_conv_ascii_case!(snake, S, "hello_world"); test_conv_ascii_case!(kebab, S, "hello-world"); test_conv_ascii_case!(shouty_snake, S, "HELLO_WORLD"); test_conv_ascii_case!(shouty_kebab, S, "HELLO-WORLD"); } { const S: &str = ""; test_conv_ascii_case!(lower_camel, S, ""); test_conv_ascii_case!(upper_camel, S, ""); test_conv_ascii_case!(title, S, ""); test_conv_ascii_case!(train, S, ""); test_conv_ascii_case!(snake, S, ""); test_conv_ascii_case!(kebab, S, ""); test_conv_ascii_case!(shouty_snake, S, ""); test_conv_ascii_case!(shouty_kebab, S, ""); } { const S: &str = "_"; test_conv_ascii_case!(lower_camel, S, ""); test_conv_ascii_case!(upper_camel, S, ""); test_conv_ascii_case!(title, S, ""); test_conv_ascii_case!(train, S, ""); test_conv_ascii_case!(snake, S, ""); test_conv_ascii_case!(kebab, S, ""); test_conv_ascii_case!(shouty_snake, S, ""); test_conv_ascii_case!(shouty_kebab, S, ""); } { const S: &str = "1.2E3"; test_conv_ascii_case!(lower_camel, S, "12e3"); test_conv_ascii_case!(upper_camel, S, "12e3"); test_conv_ascii_case!(title, S, "1 2e3"); test_conv_ascii_case!(train, S, "1-2e3"); test_conv_ascii_case!(snake, S, "1_2e3"); test_conv_ascii_case!(kebab, S, "1-2e3"); test_conv_ascii_case!(shouty_snake, S, "1_2E3"); test_conv_ascii_case!(shouty_kebab, S, "1-2E3"); } { const S: &str = "__a__b-c__d__"; test_conv_ascii_case!(lower_camel, S, "aBCD"); test_conv_ascii_case!(upper_camel, S, "ABCD"); test_conv_ascii_case!(title, S, "A B C D"); test_conv_ascii_case!(train, S, "A-B-C-D"); test_conv_ascii_case!(snake, S, "a_b_c_d"); test_conv_ascii_case!(kebab, S, "a-b-c-d"); test_conv_ascii_case!(shouty_snake, S, "A_B_C_D"); test_conv_ascii_case!(shouty_kebab, S, "A-B-C-D"); } { const S: &str = "futures-core123"; test_conv_ascii_case!(lower_camel, S, "futuresCore123"); test_conv_ascii_case!(upper_camel, S, "FuturesCore123"); test_conv_ascii_case!(title, S, "Futures Core123"); test_conv_ascii_case!(train, S, "Futures-Core123"); test_conv_ascii_case!(snake, S, "futures_core123"); test_conv_ascii_case!(kebab, S, "futures-core123"); test_conv_ascii_case!(shouty_snake, S, "FUTURES_CORE123"); test_conv_ascii_case!(shouty_kebab, S, "FUTURES-CORE123"); } } } const-str-0.7.0/src/__ctfe/chain.rs000064400000000000000000000024721046102023000152270ustar 00000000000000/// Chains multiple macro calls together. /// /// `_` is used as a placeholder for the value that is being passed through the chained calls. /// /// # Examples /// /// ``` /// use const_str::{chain, concat, replace, split}; /// /// const TOP: &str = "std"; /// /// const PARTS: &[&str] = &chain! { /// stringify!(std::sync::atomic::Ordering::Relaxed), /// replace!(_, { concat!(TOP, "::") }, ""), /// split!(_, "::"), /// }; /// /// assert_eq!(PARTS, &["sync", "atomic", "Ordering", "Relaxed"]); /// ``` #[macro_export] macro_rules! chain { ($init:expr, $( $call:ident!($($arg:tt),+), )+) => { $crate::__chain_impl!(@chain $init, $( $call!($($arg),+) ),+) }; } #[doc(hidden)] #[macro_export] macro_rules! __chain_impl { (@chain $init:expr, $call:ident!($($arg:tt),+)) => { $crate::__chain_impl!(@call $init, $call!($($arg),+)) }; (@chain $init:expr, $call:ident!($($arg:tt),+), $($rest:tt)+) => { $crate::__chain_impl!(@chain $crate::__chain_impl!(@call $init, $call!($($arg),+)), $($rest)+) }; (@call $e: expr, $call:ident!($($arg:tt),+)) => { $call!( $( $crate::__chain_impl!(@replace $e, $arg) ),+ ) }; (@replace $e:expr, _) => { $e }; (@replace $e:expr, $tt:tt) => { $tt }; } const-str-0.7.0/src/__ctfe/compare.rs000064400000000000000000000044201046102023000155660ustar 00000000000000use core::cmp::Ordering; pub struct Compare(pub T1, pub T2); impl Compare<&[u8], &[u8]> { pub const fn const_eval(&self) -> Ordering { crate::bytes::compare(self.0, self.1) } } impl Compare<&[u8; L1], &[u8; L2]> { pub const fn const_eval(&self) -> Ordering { crate::bytes::compare(self.0, self.1) } } impl Compare<&str, &str> { pub const fn const_eval(&self) -> Ordering { crate::str::compare(self.0, self.1) } } /// Compares two strings lexicographically. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// See also [`equal!`](crate::equal). /// /// # Examples /// /// ``` /// use core::cmp::Ordering; /// /// const A: &str = "1"; /// const B: &str = "10"; /// const C: &str = "2"; /// /// const ORD: Ordering = const_str::compare!(A, B); /// assert_eq!(ORD, Ordering::Less); /// /// assert!(const_str::compare!(<, A, B)); /// assert!(const_str::compare!(<=, A, B)); /// /// assert!(const_str::compare!(>, C, A)); /// assert!(const_str::compare!(>=, C, A)); /// /// assert!(const_str::compare!(==, A, A)); /// ``` /// #[macro_export] macro_rules! compare { (<, $lhs: expr, $rhs: expr) => {{ use ::core::cmp::Ordering; let ordering = $crate::__ctfe::Compare($lhs, $rhs).const_eval(); matches!(ordering, Ordering::Less) }}; (>, $lhs: expr, $rhs: expr) => {{ use ::core::cmp::Ordering; let ordering = $crate::__ctfe::Compare($lhs, $rhs).const_eval(); matches!(ordering, Ordering::Greater) }}; (==, $lhs: expr, $rhs: expr) => {{ use ::core::cmp::Ordering; let ordering = $crate::__ctfe::Compare($lhs, $rhs).const_eval(); matches!(ordering, Ordering::Equal) }}; (<=, $lhs: expr, $rhs: expr) => {{ use ::core::cmp::Ordering; let ordering = $crate::__ctfe::Compare($lhs, $rhs).const_eval(); matches!(ordering, Ordering::Less | Ordering::Equal) }}; (>=, $lhs: expr, $rhs: expr) => {{ use ::core::cmp::Ordering; let ordering = $crate::__ctfe::Compare($lhs, $rhs).const_eval(); matches!(ordering, Ordering::Greater | Ordering::Equal) }}; ($lhs: expr, $rhs: expr) => { $crate::__ctfe::Compare($lhs, $rhs).const_eval() }; } const-str-0.7.0/src/__ctfe/concat.rs000064400000000000000000000074061046102023000154160ustar 00000000000000#![allow(unsafe_code)] use super::StrBuf; pub struct Concat<'a>(pub &'a [&'a str]); impl Concat<'_> { pub const fn output_len(&self) -> usize { let mut ans = 0; let mut iter = self.0; while let [x, xs @ ..] = iter { ans += x.len(); iter = xs; } ans } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; let mut iter = self.0; while let [x, xs @ ..] = iter { let x = x.as_bytes(); let mut i = 0; while i < x.len() { buf[pos] = x[i]; pos += 1; i += 1; } iter = xs; } assert!(pos == N); unsafe { StrBuf::new_unchecked(buf) } } } /// Concatenates values into a string slice. /// /// The input type must be one of /// /// + [`&str`] /// + [`char`] /// + [`bool`] /// + [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] /// + [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ``` /// const PROMPT: &str = "The answer is"; /// const ANSWER: usize = 42; /// const MESSAGE: &str = const_str::concat!(PROMPT, " ", ANSWER); /// /// assert_eq!(MESSAGE, "The answer is 42"); /// ``` /// #[macro_export] macro_rules! concat { ($($x: expr),+ $(,)?) => {{ const STRS: &[&str] = &[$( $crate::to_str!($x) ),+]; const OUTPUT_LEN: usize = $crate::__ctfe::Concat(STRS).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Concat(STRS).const_eval(); OUTPUT_BUF.as_str() }} } pub struct Join<'a>(pub &'a [&'a str], pub &'a str); impl Join<'_> { pub const fn output_len(&self) -> usize { let mut ans = 0; let mut i = 0; while i < self.0.len() { ans += self.0[i].len(); if i < self.0.len() - 1 { ans += self.1.len(); } i += 1; } ans } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; let mut i = 0; while i < self.0.len() { let x = self.0[i].as_bytes(); let mut j = 0; while j < x.len() { buf[pos] = x[j]; pos += 1; j += 1; } if i < self.0.len() - 1 { let sep = self.1.as_bytes(); let mut j = 0; while j < sep.len() { buf[pos] = sep[j]; pos += 1; j += 1; } } i += 1; } unsafe { StrBuf::new_unchecked(buf) } } } /// Concatenates string slices into a string slice, separated by a given separator. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ``` /// const WORDS: &[&str] = &["hello", "world"]; /// const MESSAGE1: &str = const_str::join!(WORDS, " "); /// assert_eq!(MESSAGE1, "hello world"); /// /// const NUMS: &[&str] = &["1", "2", "3"]; /// const MESSAGE2: &str = const_str::join!(NUMS, ", "); /// assert_eq!(MESSAGE2, "1, 2, 3"); /// /// const EMPTY: &[&str] = &[]; /// const MESSAGE3: &str = const_str::join!(EMPTY, ", "); /// assert_eq!(MESSAGE3, ""); /// ``` #[macro_export] macro_rules! join { ($strs: expr, $sep: expr) => {{ const STRS: &[&str] = $strs; const SEP: &str = $sep; const OUTPUT_LEN: usize = $crate::__ctfe::Join(STRS, SEP).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Join(STRS, SEP).const_eval(); OUTPUT_BUF.as_str() }}; } const-str-0.7.0/src/__ctfe/concat_bytes.rs000064400000000000000000000063251046102023000166230ustar 00000000000000pub struct ConcatBytesPart(pub T); impl ConcatBytesPart { pub const fn output_len(&self) -> usize { 1 } pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(&[self.0]) } } impl ConcatBytesPart<&[u8; L]> { pub const fn output_len(&self) -> usize { L } pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(self.0) } } impl ConcatBytesPart<&[u8]> { pub const fn output_len(&self) -> usize { self.0.len() } pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(self.0) } } impl ConcatBytesPart<[u8; L]> { pub const fn output_len(&self) -> usize { L } pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(&self.0) } } impl ConcatBytesPart<&str> { pub const fn output_len(&self) -> usize { self.0.len() } pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(self.0.as_bytes()) } } pub struct ConcatBytes<'a>(pub &'a [&'a [u8]]); impl ConcatBytes<'_> { pub const fn output_len(&self) -> usize { let parts = self.0; let mut sum = 0; let mut i = 0; while i < parts.len() { sum += parts[i].len(); i += 1; } sum } pub const fn const_eval(&self) -> [u8; N] { let mut buf = [0; N]; let mut pos = 0; macro_rules! push { ($x:expr) => { buf[pos] = $x; pos += 1; }; } let parts = self.0; let mut i = 0; while i < parts.len() { let part = parts[i]; let mut j = 0; while j < part.len() { push!(part[j]); j += 1; } i += 1; } assert!(pos == N); buf } } #[doc(hidden)] #[macro_export] macro_rules! __concat_bytes_part { ($x: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::ConcatBytesPart($x).output_len(); const OUTPUT_BUF: [u8; OUTPUT_LEN] = $crate::__ctfe::ConcatBytesPart($x).const_eval(); const OUTPUT: &[u8] = &OUTPUT_BUF; OUTPUT }}; } /// Concatenates values into a byte slice. /// /// The input type must be one of /// + [`u8`] /// + [`&[u8]`](slice) /// + [`[u8; N]`](array), [`&[u8; N]`](array) /// + [`&str`](str) /// /// The output type is [`&[u8; _]`](array). /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ```rust /// const S1: &[u8; 7] = const_str::concat_bytes!(b'A', b"BC", [68, b'E', 70], "G"); /// const S2: &[u8] = const_str::concat_bytes!(S1, "/123", 0u8); /// assert_eq!(S1, b"ABCDEFG"); /// assert_eq!(S2, b"ABCDEFG/123\x00"); /// ``` /// #[macro_export] macro_rules! concat_bytes { ($($x: expr),+ $(,)?) => {{ const PARTS: &[&[u8]] = &[$( $crate::__concat_bytes_part!($x) ),+]; const OUTPUT_LEN: usize = $crate::__ctfe::ConcatBytes(PARTS).output_len(); const OUTPUT_BUF: [u8; OUTPUT_LEN] = $crate::__ctfe::ConcatBytes(PARTS).const_eval(); &OUTPUT_BUF }}; } const-str-0.7.0/src/__ctfe/cstr.rs000064400000000000000000000055011046102023000151140ustar 00000000000000#![allow(unsafe_code)] pub struct ToCStr(pub T); impl ToCStr<&str> { const fn check_nul(&self) { let bytes = self.0.as_bytes(); let mut i = 0; while i < bytes.len() { assert!(bytes[i] != 0); i += 1; } } pub const fn output_len(&self) -> usize { self.check_nul(); self.0.len() + 1 } pub const fn const_eval(&self) -> [u8; N] { let mut buf = [0; N]; let mut pos = 0; let bytes = self.0.as_bytes(); let mut i = 0; while i < bytes.len() { assert!(bytes[i] != 0); buf[pos] = bytes[i]; pos += 1; i += 1; } pos += 1; assert!(pos == N); buf } } /// Converts a string slice to [`*const c_char`](core::ffi::c_char). /// /// The C-style string is guaranteed to be terminated by a nul byte. /// This trailing nul byte will be appended by this macro. /// The provided data should not contain any nul bytes in it. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`cstr!`](crate::cstr) /// /// # Examples /// /// ``` /// use core::ffi::c_char; /// const PRINTF_FMT: *const c_char = const_str::raw_cstr!("%d\n"); /// ``` #[macro_export] macro_rules! raw_cstr { ($s: expr) => { $crate::cstr!($s).as_ptr() }; } /// Converts a string slice to [`&CStr`](core::ffi::CStr). /// /// The C-style string is guaranteed to be terminated by a nul byte. /// This trailing nul byte will be appended by this macro. /// The provided data should not contain any nul bytes in it. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`raw_cstr!`](crate::raw_cstr). /// /// Note that Rust has supported [C string literals][c-str-literal] since [1.77.0][rust-1-77-0]. /// /// [c-str-literal]: https://doc.rust-lang.org/reference/tokens.html#c-string-and-raw-c-string-literals /// [rust-1-77-0]: https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html#c-string-literals /// /// # Examples /// /// ``` /// use core::ffi::CStr;; /// const PRINTF_FMT: &CStr = const_str::cstr!("%d\n"); /// ``` #[macro_export] macro_rules! cstr { ($s:expr) => {{ const OUTPUT_LEN: ::core::primitive::usize = $crate::__ctfe::ToCStr($s).output_len(); const OUTPUT_BUF: [u8; OUTPUT_LEN] = $crate::__ctfe::ToCStr($s).const_eval(); const OUTPUT: &::core::ffi::CStr = unsafe { ::core::ffi::CStr::from_bytes_with_nul_unchecked(&OUTPUT_BUF) }; OUTPUT }}; } #[cfg(test)] mod tests { #[test] fn test_raw_cstr() { const FMT: &str = "%d\n"; let fmt = raw_cstr!(FMT); let len = FMT.len() + 1; let bytes: &[u8] = unsafe { core::slice::from_raw_parts(fmt.cast(), len) }; assert_eq!(bytes, b"%d\n\0"); } } const-str-0.7.0/src/__ctfe/encode.rs000064400000000000000000000117121046102023000153770ustar 00000000000000use crate::slice::advance; use crate::utf16::CharEncodeUtf16; pub struct Utf8Encoder { pub nul_terminated: bool, } pub struct Utf16Encoder { pub nul_terminated: bool, } pub struct Encode<'a, T>(pub &'a str, pub T); impl Encode<'_, Utf8Encoder> { pub const fn output_len(&self) -> usize { if self.1.nul_terminated { self.0.len() + 1 } else { self.0.len() } } pub const fn const_eval(&self) -> [u8; N] { let bytes = self.0.as_bytes(); if self.1.nul_terminated { let mut buf = [0; N]; let mut i = 0; while i < bytes.len() { let b = bytes[i]; assert!(b != 0); buf[i] = b; i += 1; } assert!(i + 1 == N); buf } else { crate::bytes::clone(bytes) } } } impl Encode<'_, Utf16Encoder> { pub const fn output_len(&self) -> usize { crate::utf16::str_len_utf16(self.0) + (self.1.nul_terminated as usize) } pub const fn const_eval(&self) -> [u16; N] { let mut s = self.0.as_bytes(); let mut buf = [0; N]; let mut pos = 0; while let Some((code, count)) = crate::utf8::next_char(s) { s = advance(s, count); let e = CharEncodeUtf16::new(code); buf[pos] = e.first(); pos += 1; if e.has_second() { buf[pos] = e.second(); pos += 1; } if self.1.nul_terminated { assert!(buf[pos - 1] != 0); if e.has_second() { assert!(buf[pos - 2] != 0); } } } if self.1.nul_terminated { pos += 1; } assert!(pos == N); buf } } #[doc(hidden)] #[macro_export] macro_rules! __encoder { (utf8) => {{ $crate::__ctfe::Utf8Encoder { nul_terminated: false, } }}; (utf8_z) => {{ $crate::__ctfe::Utf8Encoder { nul_terminated: true, } }}; (utf16) => {{ $crate::__ctfe::Utf16Encoder { nul_terminated: false, } }}; (utf16_z) => {{ $crate::__ctfe::Utf16Encoder { nul_terminated: true, } }}; } #[doc(hidden)] #[macro_export] macro_rules! __encode { ($e: tt, $s: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::Encode($s, $crate::__encoder!($e)).output_len(); &{ $crate::__ctfe::Encode($s, $crate::__encoder!($e)).const_eval::() } }}; } /// Encode a string slice with a specified encoding. /// /// Supported encodings: /// /// + utf8 /// + utf16 /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// ``` rust /// use const_str::encode; /// /// const S: &str = "hello你好"; /// /// const S_UTF8: &[u8] = encode!(utf8, S); /// assert_eq!(S_UTF8, [104, 101, 108, 108, 111, 228, 189, 160, 229, 165, 189]); /// /// const S_UTF16: &[u16] = encode!(utf16, S); /// assert_eq!(S_UTF16, [104, 101, 108, 108, 111, 20320, 22909]); /// ``` /// #[macro_export] macro_rules! encode { (utf8, $s: expr) => { $crate::__encode!(utf8, $s) }; (utf16, $s: expr) => { $crate::__encode!(utf16, $s) }; } /// Encode a string slice with a specified encoding and append a nul character. /// /// The provided data should not contain any nul bytes in it. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`const_str::encode!`][crate::encode] /// /// # Examples /// ``` rust /// use const_str::encode_z; /// /// const S: &str = "hello你好"; /// /// const S_UTF8_Z: &[u8] = encode_z!(utf8, S); /// assert_eq!(S_UTF8_Z, [104, 101, 108, 108, 111, 228, 189, 160, 229, 165, 189, 0]); /// /// const S_UTF16_Z: &[u16] = encode_z!(utf16, S); /// assert_eq!(S_UTF16_Z, [104, 101, 108, 108, 111, 20320, 22909, 0]); /// ``` /// #[macro_export] macro_rules! encode_z { (utf8, $s: expr) => { $crate::__encode!(utf8_z, $s) }; (utf16, $s: expr) => { $crate::__encode!(utf16_z, $s) }; } #[cfg(test)] mod tests { #[test] fn test_encode() { { const S: &str = "abc你好"; const B1: &[u8; 9] = encode!(utf8, S); const B2: &[u8] = encode!(utf8, S); const B3: &[u8; 10] = encode_z!(utf8, S); let mut ans = S.as_bytes().to_owned(); assert_eq!(B1, ans.as_slice()); assert_eq!(B2, B1); ans.push(0); assert_eq!(B3, ans.as_slice()); } { const S: &str = "abc你好𤭢"; const B1: &[u16; 7] = encode!(utf16, S); const B2: &[u16; 8] = encode_z!(utf16, S); let mut ans = S.encode_utf16().collect::>(); assert_eq!(B1, ans.as_slice()); ans.push(0); assert_eq!(B2, ans.as_slice()); } } } const-str-0.7.0/src/__ctfe/eq_ignore_ascii_case.rs000064400000000000000000000035411046102023000202560ustar 00000000000000pub struct EqIgnoreAsciiCase(pub T1, pub T2); const fn eq_ignore_ascii_case(lhs: &[u8], rhs: &[u8]) -> bool { if lhs.len() != rhs.len() { return false; } let mut i = 0; while i < lhs.len() { let l = lhs[i].to_ascii_lowercase(); let r = rhs[i].to_ascii_lowercase(); if l != r { return false; } i += 1; } true } impl EqIgnoreAsciiCase<&[u8], &[u8]> { pub const fn const_eval(&self) -> bool { eq_ignore_ascii_case(self.0, self.1) } } impl EqIgnoreAsciiCase<&str, &str> { pub const fn const_eval(&self) -> bool { eq_ignore_ascii_case(self.0.as_bytes(), self.1.as_bytes()) } } impl EqIgnoreAsciiCase<&[u8; N1], &[u8; N2]> { pub const fn const_eval(&self) -> bool { eq_ignore_ascii_case(self.0.as_slice(), self.1.as_slice()) } } /// Checks that two (string) slices are an ASCII case-insensitive match. /// /// The input type must be one of: /// + [`&str`](str) /// + [`&[u8]`](slice) /// + [`&[u8; N]`](array) /// /// The output type is [`bool`]. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// use const_str::eq_ignore_ascii_case; /// /// const _: () = { /// assert!(eq_ignore_ascii_case!("Ferris", "FERRIS")); // true /// assert!(!eq_ignore_ascii_case!(b"Ferris", b"FERRI")); // false /// /// assert!(eq_ignore_ascii_case!("Ferrös", "FERRöS")); // true /// // ^^^ ^ ^^^ ^ /// /// assert!(!eq_ignore_ascii_case!("Ferrös", "FERRÖS")); // false /// // ^ ^ /// }; /// ``` #[macro_export] macro_rules! eq_ignore_ascii_case { ($lhs:expr, $rhs:expr) => { $crate::__ctfe::EqIgnoreAsciiCase($lhs, $rhs).const_eval() }; } const-str-0.7.0/src/__ctfe/equal.rs000064400000000000000000000017201046102023000152470ustar 00000000000000pub struct Equal(pub T1, pub T2); impl Equal<&[u8], &[u8]> { pub const fn const_eval(&self) -> bool { crate::bytes::equal(self.0, self.1) } } impl Equal<&[u8; L1], &[u8; L2]> { pub const fn const_eval(&self) -> bool { crate::bytes::equal(self.0, self.1) } } impl Equal<&str, &str> { pub const fn const_eval(&self) -> bool { crate::str::equal(self.0, self.1) } } /// Checks that two strings are equal. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const A: &str = "hello"; /// const B: &str = "world"; /// const C: &str = "hello"; /// const EQ_AB: bool = const_str::equal!(A, B); /// const EQ_AC: bool = const_str::equal!(A, C); /// assert_eq!([EQ_AB, EQ_AC], [false, true]); /// #[macro_export] macro_rules! equal { ($lhs: expr, $rhs: expr) => { $crate::__ctfe::Equal($lhs, $rhs).const_eval() }; } const-str-0.7.0/src/__ctfe/find.rs000064400000000000000000000123351046102023000150640ustar 00000000000000use crate::utf8::CharEncodeUtf8; pub struct Contains<'a, P>(pub &'a str, pub P); impl Contains<'_, &str> { pub const fn const_eval(&self) -> bool { crate::str::contains(self.0, self.1) } } impl Contains<'_, char> { pub const fn const_eval(&self) -> bool { let haystack = self.0; let ch = CharEncodeUtf8::new(self.1); let needle = ch.as_str(); crate::str::contains(haystack, needle) } } /// Returns [`true`] if the given pattern matches a sub-slice of this string slice. /// /// Returns [`false`] if it does not. /// /// The pattern type must be one of /// /// + [`&str`] /// + [`char`] /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const BANANAS: &str = "bananas"; /// const A: bool = const_str::contains!(BANANAS, "nana"); /// const B: bool = const_str::contains!(BANANAS, "apples"); /// const C: bool = const_str::contains!(BANANAS, 'c'); /// assert_eq!([A, B, C], [true, false, false]); /// ``` /// #[macro_export] macro_rules! contains { ($haystack: expr, $pattern: expr) => {{ $crate::__ctfe::Contains($haystack, $pattern).const_eval() }}; } pub struct StartsWith<'a, P>(pub &'a str, pub P); impl StartsWith<'_, &str> { pub const fn const_eval(&self) -> bool { crate::str::starts_with(self.0, self.1) } } impl StartsWith<'_, char> { pub const fn const_eval(&self) -> bool { let haystack = self.0.as_bytes(); let ch = CharEncodeUtf8::new(self.1); let needle = ch.as_bytes(); crate::bytes::starts_with(haystack, needle) } } /// Returns [`true`] if the given pattern matches a prefix of this string slice. /// /// Returns [`false`] if it does not. /// /// The pattern type must be one of /// /// + [`&str`] /// + [`char`] /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const BANANAS: &str = "bananas"; /// const A: bool = const_str::starts_with!(BANANAS, "bana"); /// const B: bool = const_str::starts_with!(BANANAS, "nana"); /// const C: bool = const_str::starts_with!(BANANAS, 'b'); /// assert_eq!([A, B, C], [true, false, true]); /// ``` /// #[macro_export] macro_rules! starts_with { ($haystack: expr, $pattern: expr) => {{ $crate::__ctfe::StartsWith($haystack, $pattern).const_eval() }}; } pub struct EndsWith<'a, P>(pub &'a str, pub P); impl EndsWith<'_, &str> { pub const fn const_eval(&self) -> bool { crate::str::ends_with(self.0, self.1) } } impl EndsWith<'_, char> { pub const fn const_eval(&self) -> bool { let haystack = self.0.as_bytes(); let ch = CharEncodeUtf8::new(self.1); let needle = ch.as_bytes(); crate::bytes::ends_with(haystack, needle) } } /// Returns [`true`] if the given pattern matches a suffix of this string slice. /// /// Returns [`false`] if it does not. /// /// The pattern type must be one of /// /// + [`&str`] /// + [`char`] /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const BANANAS: &str = "bananas"; /// const A: bool = const_str::ends_with!(BANANAS, "anas"); /// const B: bool = const_str::ends_with!(BANANAS, "nana"); /// const C: bool = const_str::ends_with!(BANANAS, 's'); /// assert_eq!([A, B, C], [true, false, true]); /// ``` /// #[macro_export] macro_rules! ends_with { ($haystack: expr, $pattern: expr) => {{ $crate::__ctfe::EndsWith($haystack, $pattern).const_eval() }}; } pub struct StripPrefix<'a, P>(pub &'a str, pub P); impl<'a> StripPrefix<'a, &str> { pub const fn const_eval(&self) -> Option<&'a str> { crate::str::strip_prefix(self.0, self.1) } } pub struct StripSuffix<'a, P>(pub &'a str, pub P); impl<'a> StripSuffix<'a, &str> { pub const fn const_eval(&self) -> Option<&'a str> { crate::str::strip_suffix(self.0, self.1) } } /// Returns a string slice with the prefix removed. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// assert_eq!(const_str::strip_prefix!("foo:bar", "foo:"), Some("bar")); /// assert_eq!(const_str::strip_prefix!("foo:bar", "bar"), None); /// assert_eq!(const_str::strip_prefix!("foofoo", "foo"), Some("foo")); /// /// const FOO_BAR: &str = "foo:bar"; /// const BAR: &str = const_str::unwrap!(const_str::strip_prefix!(FOO_BAR, "foo:")); /// assert_eq!(BAR, "bar"); /// ``` /// #[macro_export] macro_rules! strip_prefix { ($s: expr, $prefix: expr) => {{ $crate::__ctfe::StripPrefix($s, $prefix).const_eval() }}; } /// Returns a string slice with the suffix removed. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// assert_eq!(const_str::strip_suffix!("bar:foo", ":foo"), Some("bar")); /// assert_eq!(const_str::strip_suffix!("bar:foo", "bar"), None); /// assert_eq!(const_str::strip_suffix!("foofoo", "foo"), Some("foo")); /// /// const FOO_BAR: &str = "foo:bar"; /// const FOO: &str = const_str::unwrap!(const_str::strip_suffix!(FOO_BAR, ":bar")); /// assert_eq!(FOO, "foo"); /// ``` /// #[macro_export] macro_rules! strip_suffix { ($s: expr, $suffix: expr) => {{ $crate::__ctfe::StripSuffix($s, $suffix).const_eval() }}; } const-str-0.7.0/src/__ctfe/fmt.rs000064400000000000000000000250111046102023000147250ustar 00000000000000#![allow(unsafe_code)] use super::StrBuf; use super::ToStr; use crate::slice::advance; use crate::utf8::CharEscapeDebug; use crate::utf8::CharEscapeDebugArgs; #[derive(Clone, Copy)] pub struct FmtSpec { pub alternate: bool, } pub struct Display(pub T, pub FmtSpec); macro_rules! delegate_display { ($($ty: ty,)+) => { $( impl Display<$ty> { pub const fn output_len(&self) -> usize { ToStr(self.0).output_len() } pub const fn const_eval(&self) -> StrBuf { ToStr(self.0).const_eval() } } )+ }; } delegate_display!(&str, char, bool, u8, u16, u32, u64, usize, i8, i16, i32, i64, isize,); #[doc(hidden)] #[macro_export] macro_rules! __fmt_display { ($x: expr, $spec: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::Display($x, $spec).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Display($x, $spec).const_eval(); OUTPUT_BUF.as_str() }}; } pub struct Debug(pub T, pub FmtSpec); macro_rules! delegate_debug { ($($ty: ty,)+) => { $( impl Debug<$ty> { pub const fn output_len(&self) -> usize { ToStr(self.0).output_len() } pub const fn const_eval(&self) -> StrBuf { ToStr(self.0).const_eval() } } )+ }; } delegate_debug!(bool, u8, u16, u32, u64, usize, i8, i16, i32, i64, isize,); impl Debug { pub const fn output_len(&self) -> usize { let escape = CharEscapeDebug::new( self.0, CharEscapeDebugArgs { escape_single_quote: true, escape_double_quote: false, }, ); escape.as_bytes().len() + 2 } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; macro_rules! push { ($x: expr) => {{ buf[pos] = $x; pos += 1; }}; } push!(b'\''); { let e = CharEscapeDebug::new( self.0, CharEscapeDebugArgs { escape_single_quote: true, escape_double_quote: false, }, ); let bytes = e.as_bytes(); let mut i = 0; while i < bytes.len() { push!(bytes[i]); i += 1; } } push!(b'\''); assert!(pos == N); unsafe { StrBuf::new_unchecked(buf) } } } impl Debug<&str> { pub const fn output_len(&self) -> usize { let mut s = self.0.as_bytes(); let mut ans = 2; while let Some((ch, count)) = crate::utf8::next_char(s) { s = advance(s, count); let e = CharEscapeDebug::new( ch, CharEscapeDebugArgs { escape_single_quote: false, escape_double_quote: true, }, ); ans += e.as_bytes().len() } ans } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; macro_rules! push { ($x: expr) => {{ buf[pos] = $x; pos += 1; }}; } push!(b'"'); let mut s = self.0.as_bytes(); while let Some((ch, count)) = crate::utf8::next_char(s) { s = advance(s, count); let e = CharEscapeDebug::new( ch, CharEscapeDebugArgs { escape_single_quote: false, escape_double_quote: true, }, ); let bytes = e.as_bytes(); let mut i = 0; while i < bytes.len() { push!(bytes[i]); i += 1; } } push!(b'"'); assert!(pos == N); unsafe { StrBuf::new_unchecked(buf) } } } #[doc(hidden)] #[macro_export] macro_rules! __fmt_debug { ($x: expr, $spec: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::Debug($x, $spec).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Debug($x, $spec).const_eval(); OUTPUT_BUF.as_str() }}; } struct Hex(T, FmtSpec, bool); pub struct LowerHex(pub T, pub FmtSpec); pub struct UpperHex(pub T, pub FmtSpec); macro_rules! impl_integer_hex { ($unsigned: ty, $signed: ty) => { impl Hex<$unsigned> { const fn output_len(&self) -> usize { let mut x = self.0; let mut ans = 0; loop { ans += 1; x /= 16; if x == 0 { break; } } if self.1.alternate { ans += 2; } ans } const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; let mut x = self.0; loop { let d = crate::ascii::num_to_hex_digit((x % 16) as u8); buf[pos] = if self.2 { d.to_ascii_uppercase() } else { d }; pos += 1; x /= 16; if x == 0 { break; } } if self.1.alternate { buf[pos] = b'x'; pos += 1; buf[pos] = b'0'; pos += 1; } assert!(pos == N); let buf = crate::bytes::reversed(buf); unsafe { StrBuf::new_unchecked(buf) } } } impl LowerHex<$unsigned> { pub const fn output_len(&self) -> usize { let h = Hex(self.0, self.1, false); h.output_len() } pub const fn const_eval(&self) -> StrBuf { let h = Hex(self.0, self.1, false); h.const_eval() } } impl UpperHex<$unsigned> { pub const fn output_len(&self) -> usize { let h = Hex(self.0, self.1, true); h.output_len() } pub const fn const_eval(&self) -> StrBuf { let h = Hex(self.0, self.1, true); h.const_eval() } } impl LowerHex<$signed> { pub const fn output_len(&self) -> usize { let h = Hex(self.0 as $unsigned, self.1, false); h.output_len() } pub const fn const_eval(&self) -> StrBuf { let h = Hex(self.0 as $unsigned, self.1, false); h.const_eval() } } impl UpperHex<$signed> { pub const fn output_len(&self) -> usize { let h = Hex(self.0 as $unsigned, self.1, true); h.output_len() } pub const fn const_eval(&self) -> StrBuf { let h = Hex(self.0 as $unsigned, self.1, true); h.const_eval() } } }; } impl_integer_hex!(u8, i8); impl_integer_hex!(u16, i16); impl_integer_hex!(u32, i32); impl_integer_hex!(u64, i64); impl_integer_hex!(u128, i128); impl_integer_hex!(usize, isize); #[doc(hidden)] #[macro_export] macro_rules! __fmt_lowerhex { ($x: expr, $spec: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::LowerHex($x, $spec).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::LowerHex($x, $spec).const_eval(); OUTPUT_BUF.as_str() }}; } #[doc(hidden)] #[macro_export] macro_rules! __fmt_upperhex { ($x: expr, $spec: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::UpperHex($x, $spec).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::UpperHex($x, $spec).const_eval(); OUTPUT_BUF.as_str() }}; } pub struct Binary(pub T, pub FmtSpec); macro_rules! impl_integer_binary { ($unsigned: ty, $signed: ty) => { impl Binary<$unsigned> { pub const fn output_len(&self) -> usize { let mut x = self.0; let mut ans = 0; loop { ans += 1; x /= 2; if x == 0 { break; } } if self.1.alternate { ans += 2; } ans } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; let mut x = self.0; loop { buf[pos] = b'0' + (x % 2) as u8; pos += 1; x /= 2; if x == 0 { break; } } if self.1.alternate { buf[pos] = b'b'; pos += 1; buf[pos] = b'0'; pos += 1; } assert!(pos == N); let buf = crate::bytes::reversed(buf); unsafe { StrBuf::new_unchecked(buf) } } } impl Binary<$signed> { pub const fn output_len(&self) -> usize { let b = Binary(self.0 as $unsigned, self.1); b.output_len() } pub const fn const_eval(&self) -> StrBuf { let b = Binary(self.0 as $unsigned, self.1); b.const_eval() } } }; } impl_integer_binary!(u8, i8); impl_integer_binary!(u16, i16); impl_integer_binary!(u32, i32); impl_integer_binary!(u64, i64); impl_integer_binary!(u128, i128); impl_integer_binary!(usize, isize); #[doc(hidden)] #[macro_export] macro_rules! __fmt_binary { ($x: expr, $spec: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::Binary($x, $spec).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Binary($x, $spec).const_eval(); OUTPUT_BUF.as_str() }}; } const-str-0.7.0/src/__ctfe/hex.rs000064400000000000000000000101041046102023000147200ustar 00000000000000pub struct Hex(pub T); struct Iter<'a> { s: &'a [u8], i: usize, } impl<'a> Iter<'a> { const fn new(s: &'a str) -> Self { Self { s: s.as_bytes(), i: 0, } } const fn next(mut self) -> (Self, Option) { let mut i = self.i; let s = self.s; macro_rules! next { () => {{ if i < s.len() { let b = s[i]; i += 1; Some(b) } else { None } }}; } while let Some(b) = next!() { let high = match b { b'0'..=b'9' => b - b'0', b'a'..=b'f' => b - b'a' + 10, b'A'..=b'F' => b - b'A' + 10, b' ' | b'\r' | b'\n' | b'\t' => continue, _ => panic!("invalid character"), }; let low = match next!() { None => panic!("expected even number of hex characters"), Some(b) => match b { b'0'..=b'9' => b - b'0', b'a'..=b'f' => b - b'a' + 10, b'A'..=b'F' => b - b'A' + 10, _ => panic!("expected hex character"), }, }; self.i = i; let val = (high << 4) | low; return (self, Some(val)); } (self, None) } } impl Hex<&[&str]> { pub const fn output_len(&self) -> usize { let mut i = 0; let mut ans = 0; while i < self.0.len() { let mut iter = Iter::new(self.0[i]); while let (next, Some(_)) = iter.next() { iter = next; ans += 1; } i += 1; } ans } pub const fn const_eval(&self) -> [u8; N] { let mut buf = [0; N]; let mut pos = 0; let mut i = 0; while i < self.0.len() { let mut iter = Iter::new(self.0[i]); while let (next, Some(val)) = iter.next() { iter = next; buf[pos] = val; pos += 1; } i += 1; } assert!(pos == N); buf } } impl Hex<&str> { pub const fn output_len(&self) -> usize { let ss: &[&str] = &[self.0]; Hex(ss).output_len() } pub const fn const_eval(&self) -> [u8; N] { let ss: &[&str] = &[self.0]; Hex(ss).const_eval() } } impl Hex<[&str; L]> { pub const fn output_len(&self) -> usize { let ss: &[&str] = &self.0; Hex(ss).output_len() } pub const fn const_eval(&self) -> [u8; N] { let ss: &[&str] = &self.0; Hex(ss).const_eval() } } /// Converts hexadecimal string slices to a byte array. /// /// It accepts the following characters in the input string: /// /// - `'0'...'9'`, `'a'...'f'`, `'A'...'F'` — hex characters which will be used /// in construction of the output byte array /// - `' '`, `'\r'`, `'\n'`, `'\t'` — formatting characters which will be /// ignored /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// ``` /// use const_str::hex; /// /// const DATA: [u8; 4] = hex!("01020304"); /// assert_eq!(DATA, [1, 2, 3, 4]); /// /// assert_eq!(hex!("a1 b2 c3 d4"), [0xA1, 0xB2, 0xC3, 0xD4]); /// assert_eq!(hex!("E5 E6 90 92"), [0xE5, 0xE6, 0x90, 0x92]); /// /// assert_eq!(hex!(["0a0B", "0C0d"]), [10, 11, 12, 13]); /// /// const S1: &str = "00010203 04050607 08090a0b 0c0d0e0f"; /// const B1: &[u8] = &hex!(S1); /// const B2: &[u8] = &hex!([ /// "00010203 04050607", // first half /// "08090a0b 0c0d0e0f", // second half /// ]); /// /// assert_eq!(B1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); /// assert_eq!(B2, B1); /// ``` #[macro_export] macro_rules! hex { ($s: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::Hex($s).output_len(); const OUTPUT_BUF: [u8; OUTPUT_LEN] = $crate::__ctfe::Hex($s).const_eval(); OUTPUT_BUF }}; } const-str-0.7.0/src/__ctfe/is_ascii.rs000064400000000000000000000024241046102023000157250ustar 00000000000000pub struct IsAscii(pub T); impl IsAscii<&[u8]> { pub const fn const_eval(&self) -> bool { let bytes = self.0; let mut i = 0; while i < bytes.len() { if !bytes[i].is_ascii() { return false; } i += 1; } true } } impl IsAscii<&str> { pub const fn const_eval(&self) -> bool { IsAscii(self.0.as_bytes()).const_eval() } } impl IsAscii<&[u8; N]> { pub const fn const_eval(&self) -> bool { IsAscii(self.0.as_slice()).const_eval() } } /// Checks if all characters in this (string) slice are within the ASCII range. /// /// The input type must be one of: /// + [`&str`](str) /// + [`&[u8]`](slice) /// + [`&[u8; N]`](array) /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const S1: &str = "hello!\n"; /// const S2: &str = "你好!"; /// /// const _: () = { /// assert!(const_str::is_ascii!(S1)); // true /// assert!(!const_str::is_ascii!(S2)); // false /// assert!(!const_str::is_ascii!(b"\x80\x00")); // false /// }; /// ``` /// #[macro_export] macro_rules! is_ascii { ($s:expr) => { $crate::__ctfe::IsAscii($s).const_eval() }; } const-str-0.7.0/src/__ctfe/net.rs000064400000000000000000000254301046102023000147320ustar 00000000000000use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; struct Parser<'a> { s: &'a [u8], i: usize, } impl<'a> Parser<'a> { const fn new(s: &'a str) -> Self { Self { s: s.as_bytes(), i: 0, } } const fn const_clone(&self) -> Self { Self { s: self.s, i: self.i, } } const fn is_end(&self) -> bool { self.i == self.s.len() } const fn peek(&self) -> Option { let &Self { s, i, .. } = self; if i < s.len() { Some(s[i]) } else { None } } const fn peek2(&self) -> Option<(u8, Option)> { let &Self { s, i, .. } = self; if i >= s.len() { return None; } if i + 1 >= s.len() { return Some((s[i], None)); } Some((s[i], Some(s[i + 1]))) } const fn read_byte(mut self) -> (Self, Option) { let Self { s, i, .. } = self; if i < s.len() { self.i += 1; (self, Some(s[i])) } else { (self, None) } } const fn read_given_byte(self, byte: u8) -> (Self, Option<()>) { let p = self.const_clone(); let (p, val) = p.read_byte(); match val { Some(v) if v == byte => (p, Some(())), _ => (self, None), } } const fn advance(mut self, step: usize) -> Self { self.i += step; self } } macro_rules! impl_read_uint { ($ty:ty, $id: ident) => { impl<'a> Parser<'a> { const fn $id( mut self, radix: u8, allow_leading_zeros: bool, max_digits: usize, ) -> (Self, Option<$ty>) { assert!(radix == 10 || radix == 16); let Self { s, mut i, .. } = self; let mut digit_count = 0; let mut ans: $ty = 0; loop { let b = if i < s.len() { s[i] } else { break }; let x = match b { b'0'..=b'9' => b - b'0', b'a'..=b'f' if radix == 16 => b - b'a' + 10, b'A'..=b'F' if radix == 16 => b - b'A' + 10, _ => break, }; if !allow_leading_zeros && (ans == 0 && digit_count == 1) { return (self, None); } ans = match ans.checked_mul(radix as $ty) { Some(x) => x, None => return (self, None), }; ans = match ans.checked_add(x as $ty) { Some(x) => x, None => return (self, None), }; i += 1; digit_count += 1; if digit_count > max_digits { return (self, None); } } if digit_count == 0 { return (self, None); } self.i = i; (self, Some(ans)) } } }; } impl_read_uint!(u8, read_u8); impl_read_uint!(u16, read_u16); macro_rules! try_parse { ($orig:ident, $id:ident, $ret: expr) => {{ match $ret { (next, Some(val)) => { $id = next; val } (_, None) => return ($orig, None), } }}; } macro_rules! parse { ($id:ident,$ret: expr) => {{ let (next, val) = $ret; $id = next; val }}; } impl Parser<'_> { const fn read_ipv4(self) -> (Self, Option) { let mut p = self.const_clone(); let mut nums = [0; 4]; let mut i = 0; while i < 4 { if i > 0 { try_parse!(self, p, p.read_given_byte(b'.')); } nums[i] = try_parse!(self, p, p.read_u8(10, false, 3)); i += 1; } let val = Ipv4Addr::new(nums[0], nums[1], nums[2], nums[3]); (p, Some(val)) } const fn read_ipv6(self) -> (Self, Option) { let mut p = self.const_clone(); let mut nums: [u16; 8] = [0; 8]; let mut left_cnt = 0; let mut right_cnt = 0; let mut state: u8 = 0; 'dfa: loop { match state { 0 => match p.peek2() { Some((b':', Some(b':'))) => { p = p.advance(2); state = 2; continue 'dfa; } _ => { state = 1; continue 'dfa; } }, 1 => loop { nums[left_cnt] = try_parse!(self, p, p.read_u16(16, true, 4)); left_cnt += 1; if left_cnt == 8 { break 'dfa; } try_parse!(self, p, p.read_given_byte(b':')); if matches!(p.peek(), Some(b':')) { p = p.advance(1); if left_cnt == 7 { break 'dfa; } state = 2; continue 'dfa; } if left_cnt == 6 { if let Some(val) = parse!(p, p.read_ipv4()) { let [n1, n2, n3, n4] = val.octets(); nums[6] = u16::from_be_bytes([n1, n2]); nums[7] = u16::from_be_bytes([n3, n4]); // left_cnt = 8; break 'dfa; } } }, 2 => loop { if left_cnt + right_cnt <= 6 { if let Some(val) = parse!(p, p.read_ipv4()) { let [n1, n2, n3, n4] = val.octets(); nums[7 - right_cnt] = u16::from_be_bytes([n1, n2]); nums[6 - right_cnt] = u16::from_be_bytes([n3, n4]); right_cnt += 2; break 'dfa; } } match parse!(p, p.read_u16(16, true, 4)) { Some(val) => { nums[7 - right_cnt] = val; right_cnt += 1; if left_cnt + right_cnt == 7 { break 'dfa; } match p.peek() { Some(b':') => p = p.advance(1), _ => break 'dfa, } } None => break 'dfa, } }, _ => unreachable!(), } } { let mut i = 8 - right_cnt; let mut j = 7; #[allow(clippy::manual_swap)] while i < j { let (lhs, rhs) = (nums[i], nums[j]); nums[i] = rhs; nums[j] = lhs; i += 1; j -= 1; } } let val = Ipv6Addr::new( nums[0], nums[1], nums[2], nums[3], // nums[4], nums[5], nums[6], nums[7], // ); (p, Some(val)) } } macro_rules! parse_with { ($s: expr, $m:ident) => {{ let p = Parser::new($s); let (p, val) = p.$m(); match val { Some(v) if p.is_end() => Some(v), _ => None, } }}; } pub const fn expect_ipv4(s: &str) -> Ipv4Addr { match parse_with!(s, read_ipv4) { Some(val) => val, None => panic!("invalid ipv4 address"), } } pub const fn expect_ipv6(s: &str) -> Ipv6Addr { match parse_with!(s, read_ipv6) { Some(val) => val, None => panic!("invalid ipv6 address"), } } pub const fn expect_ip(s: &str) -> IpAddr { match parse_with!(s, read_ipv4) { Some(val) => IpAddr::V4(val), None => match parse_with!(s, read_ipv6) { Some(val) => IpAddr::V6(val), None => panic!("invalid ip address"), }, } } /// Converts a string slice to an IP address. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// ``` /// use core::net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// use const_str::ip_addr; /// /// const LOCALHOST_V4: Ipv4Addr = ip_addr!(v4, "127.0.0.1"); /// const LOCALHOST_V6: Ipv6Addr = ip_addr!(v6, "::1"); /// /// const LOCALHOSTS: [IpAddr;2] = [ip_addr!("127.0.0.1"), ip_addr!("::1")]; /// ``` #[macro_export] macro_rules! ip_addr { (v4, $s:expr) => { $crate::__ctfe::expect_ipv4($s) }; (v6, $s:expr) => { $crate::__ctfe::expect_ipv6($s) }; ($s:expr) => { $crate::__ctfe::expect_ip($s) }; } #[cfg(test)] mod tests { use super::*; #[test] fn test_ip_addr() { fn parse(s: &str, _: &T) -> T where T: std::str::FromStr, ::Err: std::fmt::Debug, { s.parse().unwrap() } macro_rules! test_ip_addr { (v4, invalid, $s:expr) => {{ let output = parse_with!($s, read_ipv4); assert!(output.is_none()); }}; (v6, invalid, $s:expr) => {{ let output = parse_with!($s, read_ipv6); assert!(output.is_none()); }}; ($t:tt, $s:expr) => {{ let output = ip_addr!($t, $s); let ans = parse($s, &output); assert_eq!(output, ans); } { let output = ip_addr!($s); let ans = parse($s, &output); assert_eq!(output, ans); }}; } test_ip_addr!(v4, "0.0.0.0"); test_ip_addr!(v4, "127.0.0.1"); test_ip_addr!(v4, "255.255.255.255"); test_ip_addr!(v4, invalid, "0"); test_ip_addr!(v4, invalid, "0x1"); test_ip_addr!(v4, invalid, "127.00.0.1"); test_ip_addr!(v4, invalid, "027.0.0.1"); test_ip_addr!(v4, invalid, "256.0.0.1"); test_ip_addr!(v4, invalid, "255.0.0"); test_ip_addr!(v4, invalid, "255.0.0.1.2"); test_ip_addr!(v4, invalid, "255.0.0..1"); test_ip_addr!(v6, "::"); test_ip_addr!(v6, "::1"); test_ip_addr!(v6, "2001:db8::2:3:4:1"); test_ip_addr!(v6, "::1:2:3"); test_ip_addr!(v6, "FF01::101"); test_ip_addr!(v6, "0:0:0:0:0:0:13.1.68.3"); test_ip_addr!(v6, "0:0:0:0:0:FFFF:129.144.52.38"); test_ip_addr!(v6, invalid, "::::"); test_ip_addr!(v6, invalid, "::00001"); } } const-str-0.7.0/src/__ctfe/parse.rs000064400000000000000000000101411046102023000152470ustar 00000000000000use core::marker::PhantomData; pub struct Parse(T, PhantomData U>); impl Parse { pub const fn new(t: T) -> Self { Self(t, PhantomData) } } impl Parse<&str, bool> { pub const fn const_eval(&self) -> bool { if crate::str::equal(self.0, "true") { return true; } if crate::str::equal(self.0, "false") { return false; } panic!("parse error") } } impl Parse<&str, &str> { pub const fn const_eval(&self) -> &str { self.0 } } impl Parse<&str, char> { pub const fn const_eval(&self) -> char { let s = self.0.as_bytes(); if let Some((ch, count)) = crate::utf8::next_char(s) { if count == s.len() { return ch; } } panic!("parse error") } } trait IsSignedInteger { const OUTPUT: bool; } macro_rules! mark_signed_integer { ($s: expr, $($ty:ty),+) => { $( impl IsSignedInteger for $ty { const OUTPUT: bool = $s; } )+ } } mark_signed_integer!(true, i8, i16, i32, i64, i128, isize); mark_signed_integer!(false, u8, u16, u32, u64, u128, usize); macro_rules! impl_integer_parse { ($($ty: ty),+) => {$( impl Parse<&str, $ty> { pub const fn const_eval(&self) -> $ty { let s = self.0.as_bytes(); let is_signed = <$ty as IsSignedInteger>::OUTPUT; let (is_positive, digits) = match s { [] => panic!("parse error"), [x, xs @ ..] => match x { b'+' => (true, xs), b'-' if is_signed => (false, xs), _ => (true, s), }, }; let mut ans: $ty = 0; let mut i = 0; while i < digits.len() { let x = crate::ascii::num_from_dec_digit(digits[i]); match ans.checked_mul(10) { Some(val) => { let val = if is_positive { val.checked_add(x as _) } else { val.checked_sub(x as _) }; match val { Some(val) => ans = val, None => panic!("parse error"), } } None => panic!("parse error"), }; i += 1; } ans } } )+}; } impl_integer_parse!(u8, u16, u32, u64, u128, usize); impl_integer_parse!(i8, i16, i32, i64, i128, isize); /// Parse a value from a string slice. /// /// The output type must be one of /// /// + [`&str`](str) /// + [`char`] /// + [`bool`] /// + [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] /// + [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const S1: &str = "true"; /// const X1: bool = const_str::parse!(S1, bool); /// assert_eq!(X1, true); /// /// const S2: &str = "42"; /// const X2: u32 = const_str::parse!(S2, u32); /// assert_eq!(X2, 42); /// /// const S3: &str = "-1"; /// const X3: i8 = const_str::parse!(S3, i8); /// assert_eq!(X3, -1); /// ``` #[macro_export] macro_rules! parse { ($s: expr, $ty: ty) => {{ $crate::__ctfe::Parse::<_, $ty>::new($s).const_eval() }}; } #[cfg(test)] mod tests { #[test] fn test_parse() { macro_rules! test_parse { ($s: expr, $ty: tt) => {{ const OUTPUT: $ty = $crate::parse!($s, $ty); let ans: $ty = $s.parse().unwrap(); assert_eq!(OUTPUT, ans) }}; } test_parse!("true", bool); test_parse!("false", bool); test_parse!("啊", char); test_parse!("0", u8); test_parse!("-1", i8); test_parse!("+42000", u32); test_parse!("-42000", i32); } } const-str-0.7.0/src/__ctfe/repeat.rs000064400000000000000000000024741046102023000154270ustar 00000000000000#![allow(unsafe_code)] use super::StrBuf; pub struct Repeat(pub T, pub usize); impl Repeat<&str> { pub const fn const_eval(&self) -> StrBuf { let buf = bytes_repeat(self.0.as_bytes(), self.1); unsafe { StrBuf::new_unchecked(buf) } } } const fn bytes_repeat(bytes: &[u8], n: usize) -> [u8; N] { assert!(bytes.len().checked_mul(n).is_some()); assert!(bytes.len() * n == N); let mut buf = [0; N]; let mut i = 0; let mut j = 0; while i < n { let mut k = 0; while k < bytes.len() { buf[j] = bytes[k]; j += 1; k += 1; } i += 1; } buf } /// Creates a new string slice by repeating a string slice n times. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ``` /// const S: &str = "abc"; /// const SSSS: &str = const_str::repeat!(S, 4); /// assert_eq!(SSSS, "abcabcabcabc"); /// ``` /// #[macro_export] macro_rules! repeat { ($s: expr, $n: expr) => {{ const INPUT: &str = $s; const N: usize = $n; const OUTPUT_LEN: usize = INPUT.len() * N; const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Repeat(INPUT, N).const_eval(); OUTPUT_BUF.as_str() }}; } const-str-0.7.0/src/__ctfe/replace.rs000064400000000000000000000121431046102023000155540ustar 00000000000000#![allow(unsafe_code)] use crate::slice::advance; use crate::utf8::CharEncodeUtf8; use super::str::StrBuf; pub struct Replace(pub I, pub P, pub O); impl Replace<&str, &str, &str> { pub const fn output_len(&self) -> usize { let Self(mut input, replace_from, replace_to) = *self; if replace_from.is_empty() { let input_chars = crate::utf8::str_count_chars(self.0); input.len() + (input_chars + 1) * replace_to.len() } else { let mut ans = 0; while let Some((pos, remain)) = crate::str::next_match(input, replace_from) { ans += pos + replace_to.len(); input = remain; } ans += input.len(); ans } } pub const fn const_eval(&self) -> StrBuf { let Self(input, replace_from, replace_to) = *self; let mut buf = [0; N]; let mut pos = 0; macro_rules! push { ($x: expr) => {{ buf[pos] = $x; pos += 1; }}; } if replace_from.is_empty() { let mut input = input.as_bytes(); let replace_to = replace_to.as_bytes(); loop { let mut k = 0; while k < replace_to.len() { push!(replace_to[k]); k += 1; } let count = match crate::utf8::next_char(input) { Some((_, count)) => count, None => break, }; let mut i = 0; while i < count { push!(input[i]); i += 1; } input = advance(input, count); } } else { let mut input = input; let replace_to = replace_to.as_bytes(); while let Some((pos, remain)) = crate::str::next_match(input, replace_from) { let mut i = 0; while i < pos { push!(input.as_bytes()[i]); i += 1; } let mut k = 0; while k < replace_to.len() { push!(replace_to[k]); k += 1; } input = remain; } let input = input.as_bytes(); let mut i = 0; while i < input.len() { push!(input[i]); i += 1; } } assert!(pos == N); unsafe { StrBuf::new_unchecked(buf) } } } impl Replace<&str, char, &str> { pub const fn output_len(&self) -> usize { let ch = CharEncodeUtf8::new(self.1); Replace(self.0, ch.as_str(), self.2).output_len() } pub const fn const_eval(&self) -> StrBuf { let ch = CharEncodeUtf8::new(self.1); Replace(self.0, ch.as_str(), self.2).const_eval() } } /// Replaces all matches of a pattern with another string slice. /// /// See [`str::replace`](https://doc.rust-lang.org/std/primitive.str.html#method.replace). /// /// The pattern type must be one of /// /// + [`&str`](str) /// + [`char`] /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ``` /// assert_eq!("this is new", const_str::replace!("this is old", "old", "new")); /// ``` /// #[macro_export] macro_rules! replace { ($s: expr, $from: expr, $to: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::Replace($s, $from, $to).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Replace($s, $from, $to).const_eval(); OUTPUT_BUF.as_str() }}; } #[cfg(test)] mod tests { use super::*; #[test] fn test_replace() { macro_rules! testcase { ($input: expr, $from: expr, $to: expr) => {{ const OUTPUT_LEN: usize = Replace($input, $from, $to).output_len(); const OUTPUT_BUF: StrBuf = Replace($input, $from, $to).const_eval(); const OUTPUT: &str = OUTPUT_BUF.as_str(); let ans = $input.replace($from, $to); assert_eq!(OUTPUT, &*ans, "ans = {:?}", ans); assert_eq!(OUTPUT_LEN, ans.len()); }}; } testcase!("", "", ""); testcase!("", "", "a"); testcase!("", "a", ""); testcase!("", "a", "b"); testcase!("a", "", "b"); testcase!("asd", "", "b"); testcase!("aba", "a", "c"); testcase!("this is old", "old", "new"); testcase!("我", "", "1"); testcase!("我", "", "我"); testcase!("我", "我", ""); testcase!("aaaa", "aa", "bb"); testcase!("run / v4", " ", ""); testcase!("token", " ", ""); testcase!("v4 / udp", " ", ""); testcase!("v4 / upnp", "p", ""); testcase!("", 'a', ""); testcase!("", 'a', "b"); testcase!("aba", 'a', "c"); testcase!("run / v4", ' ', ""); testcase!("token", ' ', ""); testcase!("v4 / udp", ' ', ""); testcase!("我", '我', ""); } } const-str-0.7.0/src/__ctfe/sorted.rs000064400000000000000000000055421046102023000154460ustar 00000000000000const fn str_clone<'a, const N: usize>(ss: &[&'a str]) -> [&'a str; N] { assert!(ss.len() == N); let mut buf = [""; N]; let mut i = 0; while i < ss.len() { buf[i] = ss[i]; i += 1; } buf } const fn str_sorted<'a, const N: usize>(ss: &[&'a str]) -> [&'a str; N] { let mut buf = str_clone(ss); let mut l = N; while l > 1 { let mut swapped = false; let mut i = 0; while i < l - 1 { let (lhs, rhs) = (buf[i], buf[i + 1]); if crate::compare!(>, lhs, rhs) { (buf[i], buf[i + 1]) = (rhs, lhs); swapped = true; } i += 1; } if !swapped { break; } l -= 1; } buf } pub struct Sorted(pub T); impl<'a> Sorted<&[&'a str]> { pub const fn output_len(&self) -> usize { self.0.len() } pub const fn const_eval(&self) -> [&'a str; N] { str_sorted(self.0) } } impl<'a, const L: usize> Sorted<[&'a str; L]> { pub const fn output_len(&self) -> usize { L } pub const fn const_eval(&self) -> [&'a str; L] { str_sorted(&self.0) } } impl<'a, const L: usize> Sorted<&[&'a str; L]> { pub const fn output_len(&self) -> usize { L } pub const fn const_eval(&self) -> [&'a str; L] { str_sorted(self.0) } } /// Sorts string slices and returns a new array. /// /// The input type must be one of: /// + [`&[&str]`](slice) /// + [`[&str; N]`](array) /// + [`&[&str; N]`](array) /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ```rust /// const SORTED1: &[&str] = &const_str::sorted!(["one", "two", "three"]); /// assert_eq!(SORTED1, &["one", "three", "two"]); /// /// const SORTED2: [&str; 3] = const_str::sorted!(["1", "2", "10"]); /// assert_eq!(SORTED2, ["1", "10", "2"]); /// ``` /// #[macro_export] macro_rules! sorted { ($s:expr) => {{ const N: usize = $crate::__ctfe::Sorted($s).output_len(); const SS: [&str; N] = $crate::__ctfe::Sorted($s).const_eval(); SS }}; } #[cfg(test)] mod tests { fn std_sorted<'a>(iter: impl IntoIterator) -> Vec<&'a str> { let mut v: Vec<_> = iter.into_iter().collect(); v.sort_unstable(); v } #[test] fn test_sorted() { macro_rules! testcase { ($s:expr) => { let const_sorted = sorted!($s); let std_sorted = std_sorted($s); assert_eq!(const_sorted, &*std_sorted); }; } testcase!([]); testcase!(["a"]); testcase!(["a", "a"]); testcase!(["b", "a"]); testcase!(["a", "b", "c"]); testcase!(["b", "a", "c"]); testcase!(["c", "b", "a"]); testcase!(["1", "2", "10", "20", "3"]); } } const-str-0.7.0/src/__ctfe/split.rs000064400000000000000000000564141046102023000153050ustar 00000000000000use crate::slice::advance; use crate::slice::subslice; use crate::utf8::CharEncodeUtf8; use core::str; struct SplitImpl<'input, 'pat> { input: &'input str, pattern: &'pat str, inclusive: bool, } impl<'input> SplitImpl<'input, '_> { const fn output_len(&self) -> usize { let mut input = self.input; let pat = self.pattern; if pat.is_empty() { crate::utf8::str_count_chars(input) + 2 } else { let mut ans = 0; while let Some((_, remain)) = crate::str::next_match(input, pat) { ans += 1; input = remain } if self.inclusive { if !input.is_empty() { ans += 1; } } else { ans += 1; } ans } } #[allow(unsafe_code)] const fn const_eval(&self) -> [&'input str; N] { let mut input = self.input; let pat = self.pattern; let mut buf: [&str; N] = [""; N]; let mut pos = 0; if pat.is_empty() { let mut input = input.as_bytes(); { buf[pos] = unsafe { str::from_utf8_unchecked(subslice(input, 0..0)) }; pos += 1; } while let Some((_, count)) = crate::utf8::next_char(input) { buf[pos] = unsafe { str::from_utf8_unchecked(subslice(input, 0..count)) }; pos += 1; input = advance(input, count); } { buf[pos] = unsafe { str::from_utf8_unchecked(subslice(input, 0..0)) }; pos += 1; } } else { while let Some((m, remain)) = crate::str::next_match(input, pat) { let substr = if self.inclusive { subslice(input.as_bytes(), 0..m + pat.len()) } else { subslice(input.as_bytes(), 0..m) }; buf[pos] = unsafe { str::from_utf8_unchecked(substr) }; pos += 1; input = remain; } if self.inclusive { if !input.is_empty() { buf[pos] = input; pos += 1; } } else { buf[pos] = input; pos += 1; } } assert!(pos == N); buf } } pub struct Split(pub T, pub P); impl<'input, 'pat> Split<&'input str, &'pat str> { const fn to_impl(&self) -> SplitImpl<'input, 'pat> { SplitImpl { input: self.0, pattern: self.1, inclusive: false, } } pub const fn output_len(&self) -> usize { self.to_impl().output_len() } pub const fn const_eval(&self) -> [&'input str; N] { self.to_impl().const_eval() } } impl<'input> Split<&'input str, char> { const fn to_impl<'pat>(&self, ch: &'pat CharEncodeUtf8) -> SplitImpl<'input, 'pat> { SplitImpl { input: self.0, pattern: ch.as_str(), inclusive: false, } } pub const fn output_len(&self) -> usize { let ch = CharEncodeUtf8::new(self.1); self.to_impl(&ch).output_len() } pub const fn const_eval(&self) -> [&'input str; N] { let ch = CharEncodeUtf8::new(self.1); self.to_impl(&ch).const_eval() } } impl<'input> Split<&'input str, &[char]> { const fn char_in_slice(&self, ch: char) -> bool { let chars = self.1; let mut i = 0; while i < chars.len() { if chars[i] == ch { return true; } i += 1; } false } pub const fn output_len(&self) -> usize { let mut input = self.0.as_bytes(); let mut ans = 0; if self.1.is_empty() { return 1; // No splitting happens if no chars to split on } while let Some((ch, count)) = crate::utf8::next_char(input) { if self.char_in_slice(ch) { ans += 1; } input = advance(input, count); } ans + 1 // Add 1 for the final segment } #[allow(unsafe_code)] pub const fn const_eval(&self) -> [&'input str; N] { let mut input = self.0.as_bytes(); let input_str = self.0; let mut buf: [&str; N] = [""; N]; let mut pos = 0; let mut start_byte_pos = 0; let mut current_byte_pos = 0; if self.1.is_empty() { // No chars to split on, return the whole string buf[0] = input_str; assert!(1 == N); return buf; } while let Some((ch, count)) = crate::utf8::next_char(input) { if self.char_in_slice(ch) { // Found a split character, extract substring from start_byte_pos to current_byte_pos let substr_bytes = subslice(input_str.as_bytes(), start_byte_pos..current_byte_pos); buf[pos] = unsafe { core::str::from_utf8_unchecked(substr_bytes) }; pos += 1; // Update start position for next substring (skip the split character) start_byte_pos = current_byte_pos + count; } current_byte_pos += count; input = advance(input, count); } // Add the final segment let substr_bytes = subslice(input_str.as_bytes(), start_byte_pos..input_str.len()); buf[pos] = unsafe { core::str::from_utf8_unchecked(substr_bytes) }; pos += 1; assert!(pos == N); buf } } /// Returns an array of substrings of a string slice, separated by characters matched by a pattern. /// /// The pattern type must be one of /// /// + [`&str`](prim@str) /// + [`char`] /// + [`&[char]`](slice) /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`str::split`](https://doc.rust-lang.org/std/primitive.str.html#method.split). /// /// # Examples /// /// ``` /// const SEPARATOR: &str = ", "; /// const TEXT: &str = "lion, tiger, leopard"; /// /// const ANIMALS_ARRAY: [&str;3] = const_str::split!(TEXT, SEPARATOR); /// const ANIMALS_SLICE: &[&str] = &const_str::split!(TEXT, SEPARATOR); /// /// assert_eq!(ANIMALS_ARRAY, ANIMALS_SLICE); /// assert_eq!(ANIMALS_SLICE, &["lion", "tiger", "leopard"]); /// ``` /// /// Split by any character in a slice: /// ``` /// const TEXT: &str = "one:two;three,four"; /// const SEPARATORS: &[char] = &[':', ';', ',']; /// const WORDS: &[&str] = &const_str::split!(TEXT, SEPARATORS); /// /// assert_eq!(WORDS, &["one", "two", "three", "four"]); /// ``` #[macro_export] macro_rules! split { ($s: expr, $pat: expr) => {{ const INPUT: &str = $s; const OUTPUT_LEN: usize = $crate::__ctfe::Split(INPUT, $pat).output_len(); const OUTPUT_BUF: [&str; OUTPUT_LEN] = $crate::__ctfe::Split(INPUT, $pat).const_eval(); OUTPUT_BUF }}; } pub struct SplitInclusive(pub T, pub P); impl<'input, 'pat> SplitInclusive<&'input str, &'pat str> { const fn to_impl(&self) -> SplitImpl<'input, 'pat> { SplitImpl { input: self.0, pattern: self.1, inclusive: true, } } pub const fn output_len(&self) -> usize { self.to_impl().output_len() } pub const fn const_eval(&self) -> [&'input str; N] { self.to_impl().const_eval() } } impl<'input> SplitInclusive<&'input str, char> { const fn to_impl<'pat>(&self, ch: &'pat CharEncodeUtf8) -> SplitImpl<'input, 'pat> { SplitImpl { input: self.0, pattern: ch.as_str(), inclusive: true, } } pub const fn output_len(&self) -> usize { let ch = CharEncodeUtf8::new(self.1); self.to_impl(&ch).output_len() } pub const fn const_eval(&self) -> [&'input str; N] { let ch = CharEncodeUtf8::new(self.1); self.to_impl(&ch).const_eval() } } impl<'input> SplitInclusive<&'input str, &[char]> { const fn char_in_slice(&self, ch: char) -> bool { let chars = self.1; let mut i = 0; while i < chars.len() { if chars[i] == ch { return true; } i += 1; } false } pub const fn output_len(&self) -> usize { if self.0.is_empty() { return 0; // Empty string always returns empty result for split_inclusive } let mut input = self.0.as_bytes(); let mut ans = 0; if self.1.is_empty() { return 1; // No splitting happens if no chars to split on } let mut found_any_split = false; while let Some((ch, count)) = crate::utf8::next_char(input) { if self.char_in_slice(ch) { ans += 1; found_any_split = true; } input = advance(input, count); } if !found_any_split { return 1; // If no splits, return whole string } // Check if the last character was a split character let mut input_check = self.0.as_bytes(); let mut last_was_split = false; while let Some((ch, count)) = crate::utf8::next_char(input_check) { let remaining = advance(input_check, count); if remaining.is_empty() { // This is the last character last_was_split = self.char_in_slice(ch); break; } input_check = remaining; } if !last_was_split { ans += 1; } ans } #[allow(unsafe_code)] pub const fn const_eval(&self) -> [&'input str; N] { if self.0.is_empty() { // Empty string returns empty array for split_inclusive let buf: [&str; N] = [""; N]; assert!(N == 0); return buf; } let mut input = self.0.as_bytes(); let input_str = self.0; let mut buf: [&str; N] = [""; N]; let mut pos = 0; let mut start_byte_pos = 0; let mut current_byte_pos = 0; if self.1.is_empty() { // No chars to split on, return the whole string buf[0] = input_str; assert!(1 == N); return buf; } while let Some((ch, count)) = crate::utf8::next_char(input) { current_byte_pos += count; if self.char_in_slice(ch) { // Found a split character, extract substring from start_byte_pos to current_byte_pos (inclusive) let substr_bytes = subslice(input_str.as_bytes(), start_byte_pos..current_byte_pos); buf[pos] = unsafe { core::str::from_utf8_unchecked(substr_bytes) }; pos += 1; // Update start position for next substring start_byte_pos = current_byte_pos; } input = advance(input, count); } // Add the final segment if there's remaining content if start_byte_pos < input_str.len() { let substr_bytes = subslice(input_str.as_bytes(), start_byte_pos..input_str.len()); buf[pos] = unsafe { core::str::from_utf8_unchecked(substr_bytes) }; pos += 1; } assert!(pos == N); buf } } /// Returns an array of substrings of a string slice, separated by characters matched by a pattern. /// /// Differs from the array produced by [`split!`] in that /// [`split_inclusive!`](crate::split_inclusive) leaves the matched part as the terminator of the substring. /// /// If the last element of the string is matched, /// that element will be considered the terminator of the preceding substring. /// That substring will be the last item returned by the iterator. /// /// The pattern type must be one of /// /// + [`&str`](prim@str) /// + [`char`] /// + [`&[char]`](slice) /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`str::split_inclusive`](https://doc.rust-lang.org/std/primitive.str.html#method.split_inclusive). /// /// # Examples /// ``` /// const TEXT: &str = "Mary had a little lamb\nlittle lamb\nlittle lamb."; /// const ANSWER:&[&str] = &const_str::split_inclusive!(TEXT, "\n"); /// assert_eq!(ANSWER, &["Mary had a little lamb\n", "little lamb\n", "little lamb."]); /// ``` /// ``` /// const TEXT: &str = "\nA\nB\nC\n"; /// const ANSWER:&[&str] = &const_str::split_inclusive!(TEXT, "\n"); /// assert_eq!(ANSWER, &["\n", "A\n", "B\n", "C\n"]); /// ``` /// /// Split by any character in a slice (inclusive): /// ``` /// const TEXT: &str = "one:two;three,four"; /// const SEPARATORS: &[char] = &[':', ';', ',']; /// const WORDS: &[&str] = &const_str::split_inclusive!(TEXT, SEPARATORS); /// /// assert_eq!(WORDS, &["one:", "two;", "three,", "four"]); /// ``` #[macro_export] macro_rules! split_inclusive { ($s: expr, $pat: expr) => {{ const INPUT: &str = $s; const OUTPUT_LEN: usize = $crate::__ctfe::SplitInclusive(INPUT, $pat).output_len(); const OUTPUT_BUF: [&str; OUTPUT_LEN] = $crate::__ctfe::SplitInclusive(INPUT, $pat).const_eval(); OUTPUT_BUF }}; } pub struct SplitAsciiWhitespace(pub T); impl SplitAsciiWhitespace<&'_ str> { pub const fn output_len(&self) -> usize { let bytes = self.0.as_bytes(); let mut count = 0; let mut i = 0; let mut in_word = false; while i < bytes.len() { if bytes[i].is_ascii_whitespace() { if in_word { count += 1; in_word = false; } } else { in_word = true; } i += 1; } if in_word { count += 1; } count } #[allow(unsafe_code)] pub const fn const_eval(&self) -> [&'_ str; N] { let bytes = self.0.as_bytes(); let mut buf: [&str; N] = [""; N]; let mut pos = 0; let mut i = 0; while i < bytes.len() { // Skip leading whitespace while i < bytes.len() && bytes[i].is_ascii_whitespace() { i += 1; } if i >= bytes.len() { break; } // Mark start of word let start = i; // Find end of word while i < bytes.len() && !bytes[i].is_ascii_whitespace() { i += 1; } // Extract word let word_bytes = subslice(bytes, start..i); buf[pos] = unsafe { core::str::from_utf8_unchecked(word_bytes) }; pos += 1; } assert!(pos == N); buf } } pub const fn map_lines(mut lines: [&str; N]) -> [&str; N] { let mut i = 0; while i < N { let s = lines[i]; match crate::str::strip_suffix(s, "\r\n") { Some(s) => lines[i] = s, None => match crate::str::strip_suffix(s, "\n") { Some(s) => lines[i] = s, None => lines[i] = s, }, } i += 1; } lines } /// Returns an array of the lines in a string. /// /// Lines are split by LF (`\n`) or CRLF (`\r\n`). /// /// Line terminators are not included in the returned array. /// /// The final line ending is optional. /// A string that ends with a final line ending will return the same lines /// as an otherwise identical string without a final line ending. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`str::lines`](https://doc.rust-lang.org/std/primitive.str.html#method.lines) /// /// # Examples /// ```rust /// const TEXT: &str = "foo\r\nbar\n\nbaz\r"; /// const LINES_ARRAY: [&str;4] = const_str::split_lines!(TEXT); /// const LINES_SLICE: &[&str] = &const_str::split_lines!(TEXT); /// /// assert_eq!(LINES_ARRAY, LINES_SLICE); /// assert_eq!(LINES_SLICE, &["foo", "bar", "", "baz\r"]); /// ``` /// ```rust /// const TEXT1: &str = "1\r\n2\r\n3\r\n"; /// const TEXT2: &str = "1\n2\n3\n"; /// const TEXT3: &str = "1\n2\n3"; /// const LINES1: &[&str] = &const_str::split_lines!(TEXT1); /// const LINES2: &[&str] = &const_str::split_lines!(TEXT2); /// const LINES3: &[&str] = &const_str::split_lines!(TEXT3); /// assert_eq!(LINES1, LINES2); /// assert_eq!(LINES2, LINES3); /// ``` #[macro_export] macro_rules! split_lines { ($s: expr) => {{ $crate::__ctfe::map_lines($crate::split_inclusive!($s, "\n")) }}; } /// Returns an array of substrings of a string slice, separated by ASCII whitespace. /// /// ASCII whitespace characters are: space (` `), tab (`\t`), newline (`\n`), /// carriage return (`\r`), and form feed (`\f`). /// /// Consecutive whitespace characters are treated as a single separator. /// Leading and trailing whitespace is ignored. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// See also [`str::split_ascii_whitespace`](https://doc.rust-lang.org/std/primitive.str.html#method.split_ascii_whitespace). /// /// # Examples /// /// ``` /// const TEXT: &str = " hello world "; /// const WORDS_ARRAY: [&str; 2] = const_str::split_ascii_whitespace!(TEXT); /// const WORDS_SLICE: &[&str] = &const_str::split_ascii_whitespace!(TEXT); /// /// assert_eq!(WORDS_ARRAY, WORDS_SLICE); /// assert_eq!(WORDS_SLICE, &["hello", "world"]); /// ``` /// /// ``` /// const TEXT: &str = "word1\t\tword2\n\nword3"; /// const WORDS: &[&str] = &const_str::split_ascii_whitespace!(TEXT); /// assert_eq!(WORDS, &["word1", "word2", "word3"]); /// ``` #[macro_export] macro_rules! split_ascii_whitespace { ($s: expr) => {{ const INPUT: &str = $s; const OUTPUT_LEN: usize = $crate::__ctfe::SplitAsciiWhitespace(INPUT).output_len(); const OUTPUT_BUF: [&str; OUTPUT_LEN] = $crate::__ctfe::SplitAsciiWhitespace(INPUT).const_eval(); OUTPUT_BUF }}; } #[cfg(test)] mod tests { use super::*; #[test] fn test_split() { macro_rules! testcase { ($input: expr, $pat: expr) => {{ const OUTPUT: &[&str] = &$crate::split!($input, $pat); let ans = $input.split($pat).collect::>(); assert_eq!(OUTPUT.len(), ans.len()); assert_eq!(OUTPUT, &*ans, "ans = {:?}", ans); }}; } testcase!("", ""); testcase!("a中1😂1!", ""); testcase!("a中1😂1!", "a"); testcase!("a中1😂1!", "中"); testcase!("a中1😂1!", "1"); testcase!("a中1😂1!", "😂"); testcase!("a中1😂1!", "!"); testcase!("11111", "1"); testcase!("222", "22"); testcase!("啊哈哈哈", "哈哈"); testcase!("some string:another string", ":"); testcase!("11111", '1'); testcase!("a中1😂1!", 'a'); testcase!("a中1😂1!", '中'); testcase!("a中1😂1!", '1'); testcase!("a中1😂1!", '😂'); testcase!("a中1😂1!", '!'); } #[test] fn test_split_char_slice() { macro_rules! testcase_char_slice { ($input: expr, $chars: expr) => {{ const CHARS: &[char] = $chars; const OUTPUT: &[&str] = &$crate::split!($input, CHARS); let ans = $input.split(CHARS).collect::>(); assert_eq!( OUTPUT.len(), ans.len(), "Length mismatch for input: {:?}, chars: {:?}", $input, CHARS ); assert_eq!( OUTPUT, &*ans, "Content mismatch for input: {:?}, chars: {:?}, expected: {:?}", $input, CHARS, ans ); }}; } // Start with basic cases testcase_char_slice!("a,b,c", &[',']); testcase_char_slice!("hello", &[]); testcase_char_slice!("", &[]); testcase_char_slice!("", &[',']); // More complex cases testcase_char_slice!("hello,world;test", &[',', ';']); testcase_char_slice!("hello", &['x', 'y', 'z']); testcase_char_slice!("a,b,,c,", &[',']); testcase_char_slice!(";;;", &[';']); // Unicode characters testcase_char_slice!("a中1😂1!", &['中', '😂']); testcase_char_slice!("hello世界test", &['世', '界']); // Complex patterns testcase_char_slice!("one:two;three,four", &[':', ';', ',']); } #[test] fn test_split_inclusive_char_slice() { macro_rules! testcase_inclusive_char_slice { ($input: expr, $chars: expr) => {{ const CHARS: &[char] = $chars; const OUTPUT: &[&str] = &$crate::split_inclusive!($input, CHARS); let ans = $input.split_inclusive(CHARS).collect::>(); assert_eq!( OUTPUT.len(), ans.len(), "Length mismatch for input: {:?}, chars: {:?}", $input, CHARS ); assert_eq!( OUTPUT, &*ans, "Content mismatch for input: {:?}, chars: {:?}, expected: {:?}", $input, CHARS, ans ); }}; } // Start with basic cases testcase_inclusive_char_slice!("a,b,c", &[',']); testcase_inclusive_char_slice!("hello", &[]); testcase_inclusive_char_slice!("", &[]); // More cases testcase_inclusive_char_slice!("hello,world;test", &[',', ';']); testcase_inclusive_char_slice!("hello", &['x', 'y', 'z']); testcase_inclusive_char_slice!("a,b,,c,", &[',']); testcase_inclusive_char_slice!(";;;", &[';']); // Unicode characters testcase_inclusive_char_slice!("a中1😂1!", &['中', '😂']); testcase_inclusive_char_slice!("hello世界test", &['世', '界']); // Complex patterns testcase_inclusive_char_slice!("one:two;three,four", &[':', ';', ',']); } #[test] fn test_split_ascii_whitespace() { macro_rules! testcase { ($input: expr) => {{ const OUTPUT: &[&str] = &$crate::split_ascii_whitespace!($input); let ans = $input.split_ascii_whitespace().collect::>(); assert_eq!( OUTPUT.len(), ans.len(), "Length mismatch for input: {:?}", $input ); assert_eq!( OUTPUT, &*ans, "Content mismatch for input: {:?}, expected: {:?}", $input, ans ); }}; } // Basic cases testcase!(""); testcase!(" "); testcase!(" "); testcase!("hello"); testcase!(" hello "); testcase!(" hello "); testcase!("hello world"); testcase!(" hello world "); testcase!(" hello world "); // Different whitespace types testcase!("a\tb\nc\rd\x0Cf"); testcase!(" \t\n\r\x0C "); testcase!("word1\t\t\tword2\n\n\nword3"); // Mixed content testcase!("foo bar baz"); testcase!("\tfoo\nbar\rbaz\x0C"); testcase!(" a b c "); testcase!("\t\n\r\x0C"); // Edge cases testcase!("single"); testcase!("a"); testcase!("a b"); testcase!(" a b "); } } const-str-0.7.0/src/__ctfe/squish.rs000064400000000000000000000101451046102023000154550ustar 00000000000000#![allow(unsafe_code)] use crate::__ctfe::StrBuf; pub struct Squish(pub T); impl Squish<&'_ str> { pub const fn output_len(&self) -> usize { let mut len = 0; macro_rules! push { ($x: expr) => { len += 1; }; } let bytes = self.0.as_bytes(); let mut i = 0; while i < bytes.len() { let x = bytes[i]; if x.is_ascii_whitespace() { let mut j = i + 1; while j < bytes.len() { if bytes[j].is_ascii_whitespace() { j += 1; } else { break; } } if !(i == 0 || j == bytes.len()) { push!(b' '); } i = j; continue; } push!(x); i += 1; } len } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; macro_rules! push { ($x: expr) => { buf[pos] = $x; pos += 1; }; } let bytes = self.0.as_bytes(); let mut i = 0; while i < bytes.len() { let x = bytes[i]; if x.is_ascii_whitespace() { let mut j = i + 1; while j < bytes.len() { if bytes[j].is_ascii_whitespace() { j += 1; } else { break; } } if !(i == 0 || j == bytes.len()) { push!(b' '); } i = j; continue; } push!(x); i += 1; } assert!(pos == N); unsafe { StrBuf::new_unchecked(buf) } } } /// Splits the string by ASCII whitespaces, and then joins the parts with a single space. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ```rust /// use const_str::squish; /// /// assert_eq!(squish!(" SQUISH \t THAT \t CAT! "), "SQUISH THAT CAT!"); /// /// const SQL: &str = squish!( /// "SELECT /// name, /// created_at, /// updated_at /// FROM users /// WHERE id = ?" /// ); /// assert_eq!(SQL, "SELECT name, created_at, updated_at FROM users WHERE id = ?"); /// ``` /// #[macro_export] macro_rules! squish { ($s:expr) => {{ const INPUT: &str = $s; const N: usize = $crate::__ctfe::Squish(INPUT).output_len(); const OUTPUT: $crate::__ctfe::StrBuf = $crate::__ctfe::Squish(INPUT).const_eval(); OUTPUT.as_str() }}; } #[cfg(test)] mod tessts { fn join<'a>(iter: impl IntoIterator, sep: &str) -> String { let mut ans = String::new(); let mut iter = iter.into_iter(); match iter.next() { None => return ans, Some(first) => ans.push_str(first), } for part in iter { ans.push_str(sep); ans.push_str(part); } ans } fn std_squish(input: &str) -> String { join(input.split_ascii_whitespace(), " ") } #[test] fn test_squish() { macro_rules! testcase { ($s:expr) => {{ const OUTPUT: &str = squish!($s); let expected = std_squish($s); assert_eq!(OUTPUT, expected); }}; } testcase!(""); testcase!(" "); testcase!(" t"); testcase!("t "); testcase!(" t "); testcase!(" t t"); testcase!(" SQUISH \t THAT \t CAT "); testcase!( " All you need to know is to \t SQUISH THAT CAT! \ " ); testcase!(concat!("We\n", "always\n", "SQUISH\n", "THAT\n", "CAT.")); testcase!( "SELECT name, created_at, updated_at FROM users WHERE id = ?" ); } } const-str-0.7.0/src/__ctfe/str.rs000064400000000000000000000024461046102023000147560ustar 00000000000000#![allow(unsafe_code)] pub struct StrBuf([u8; N]); impl StrBuf { /// # Safety /// `buf` must contain valid utf-8 bytes. pub const unsafe fn new_unchecked(buf: [u8; N]) -> Self { #[cfg(debug_assertions)] { assert!(core::str::from_utf8(&buf).is_ok()) } Self(buf) } // const since 1.55 pub const fn as_str(&self) -> &str { unsafe { core::str::from_utf8_unchecked(&self.0) } } pub const fn as_bytes(&self) -> &[u8] { &self.0 } pub const fn from_str(s: &str) -> Self { let buf = crate::bytes::clone::(s.as_bytes()); unsafe { Self::new_unchecked(buf) } } } /// Converts a byte string to a string slice /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// ``` /// const BYTE_PATH: &[u8] = b"/tmp/file"; /// const PATH: &str = const_str::from_utf8!(BYTE_PATH); /// /// assert_eq!(PATH, "/tmp/file"); /// ``` /// #[macro_export] macro_rules! from_utf8 { ($s: expr) => {{ use ::core::primitive::str; // const since 1.63 const OUTPUT: &str = match ::core::str::from_utf8($s) { Ok(s) => s, Err(_) => panic!("invalid utf-8 bytes"), }; OUTPUT }}; } const-str-0.7.0/src/__ctfe/to_byte_array.rs000064400000000000000000000016371046102023000170120ustar 00000000000000pub struct ToByteArray(pub T); impl ToByteArray<&str> { pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(self.0.as_bytes()) } } impl ToByteArray<&[u8; L]> { pub const fn const_eval(&self) -> [u8; N] { crate::bytes::clone(self.0) } } /// Converts a string slice or a byte string to a byte array. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// ``` /// const S: &str = "hello"; /// const B: &[u8; 6] = b"hello\0"; /// assert_eq!(const_str::to_byte_array!(S), [b'h', b'e', b'l', b'l', b'o']); /// assert_eq!(const_str::to_byte_array!(B), [b'h', b'e', b'l', b'l', b'o', b'\0']); /// ``` /// #[macro_export] macro_rules! to_byte_array { ($s: expr) => {{ const OUTPUT_LEN: usize = $s.len(); $crate::__ctfe::ToByteArray($s).const_eval::() }}; } const-str-0.7.0/src/__ctfe/to_char_array.rs000064400000000000000000000015321046102023000167560ustar 00000000000000pub struct ToCharArray(pub T); impl ToCharArray<&str> { pub const fn output_len(&self) -> usize { crate::utf8::str_count_chars(self.0) } pub const fn const_eval(&self) -> [char; N] { crate::utf8::str_chars(self.0) } } /// Converts a string slice into an array of its characters. /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// ``` /// const CHARS: [char; 5] = const_str::to_char_array!("Hello"); /// assert_eq!(CHARS, ['H', 'e', 'l', 'l', 'o']); /// ``` /// #[macro_export] macro_rules! to_char_array { ($s: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::ToCharArray($s).output_len(); const OUTPUT_BUF: [char; OUTPUT_LEN] = $crate::__ctfe::ToCharArray($s).const_eval::(); OUTPUT_BUF }}; } const-str-0.7.0/src/__ctfe/to_str.rs000064400000000000000000000135471046102023000154640ustar 00000000000000#![allow(unsafe_code)] use super::str::StrBuf; use crate::utf8::CharEncodeUtf8; pub struct ToStr(pub T); impl ToStr<&str> { pub const fn output_len(&self) -> usize { self.0.len() } pub const fn const_eval(&self) -> StrBuf { StrBuf::from_str(self.0) } } impl ToStr { const fn bool_to_str(b: bool) -> &'static str { if b { "true" } else { "false" } } pub const fn output_len(&self) -> usize { Self::bool_to_str(self.0).len() } pub const fn const_eval(&self) -> StrBuf { let bytes: &[u8] = Self::bool_to_str(self.0).as_bytes(); let buf = crate::bytes::merge([0; N], bytes); unsafe { StrBuf::new_unchecked(buf) } } } impl ToStr { pub const fn output_len(&self) -> usize { self.0.len_utf8() } pub const fn const_eval(&self) -> StrBuf { let ch = CharEncodeUtf8::new(self.0); let buf = crate::bytes::merge([0; N], ch.as_bytes()); unsafe { StrBuf::new_unchecked(buf) } } } macro_rules! impl_integer_to_str { ($unsigned: ty, $signed: ty) => { impl ToStr<$unsigned> { pub const fn output_len(&self) -> usize { let mut x = self.0; let mut ans = 1; while x > 9 { ans += 1; x /= 10; } ans } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; let mut x = self.0; loop { buf[pos] = b'0' + (x % 10) as u8; pos += 1; x /= 10; if x == 0 { break; } } assert!(pos == N); let buf = crate::bytes::reversed(buf); unsafe { StrBuf::new_unchecked(buf) } } } impl ToStr<$signed> { pub const fn output_len(&self) -> usize { let x = self.0; let abs_len = ToStr(x.unsigned_abs()).output_len(); abs_len + (x < 0) as usize } pub const fn const_eval(&self) -> StrBuf { let mut buf = [0; N]; let mut pos = 0; let mut x = self.0.unsigned_abs(); loop { buf[pos] = b'0' + (x % 10) as u8; pos += 1; x /= 10; if x == 0 { break; } } if self.0 < 0 { buf[pos] = b'-'; pos += 1; } assert!(pos == N); let buf = crate::bytes::reversed(buf); unsafe { StrBuf::new_unchecked(buf) } } } }; } impl_integer_to_str!(u8, i8); impl_integer_to_str!(u16, i16); impl_integer_to_str!(u32, i32); impl_integer_to_str!(u64, i64); impl_integer_to_str!(u128, i128); impl_integer_to_str!(usize, isize); /// Converts a value to a string slice. /// /// The input type must be one of /// /// + [`&str`] /// + [`char`] /// + [`bool`] /// + [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] /// + [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] /// /// This macro is [const-context only](./index.html#const-context-only). /// /// # Examples /// /// ``` /// const A: &str = const_str::to_str!("A"); /// assert_eq!(A, "A"); /// /// const B: &str = const_str::to_str!('我'); /// assert_eq!(B, "我"); /// /// const C: &str = const_str::to_str!(true); /// assert_eq!(C, "true"); /// /// const D: &str = const_str::to_str!(1_u8 + 1); /// assert_eq!(D, "2"); /// /// const E: &str = const_str::to_str!(-21_i32 * 2); /// assert_eq!(E, "-42") /// ``` /// #[macro_export] macro_rules! to_str { ($x: expr) => {{ const OUTPUT_LEN: usize = $crate::__ctfe::ToStr($x).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::ToStr($x).const_eval(); OUTPUT_BUF.as_str() }}; } #[cfg(test)] mod tests { use super::*; #[test] fn test_to_str() { macro_rules! test_to_str { ($ty: ty, $x: expr) => {{ const X: $ty = $x; const OUTPUT_LEN: usize = ToStr(X).output_len(); const OUTPUT_BUF: StrBuf = ToStr(X).const_eval(); let output = OUTPUT_BUF.as_str(); let ans = X.to_string(); assert_eq!(OUTPUT_LEN, ans.len()); assert_eq!(output, ans); }}; } test_to_str!(&str, "lovelive superstar"); test_to_str!(bool, true); test_to_str!(bool, false); test_to_str!(char, '鲤'); test_to_str!(char, '鱼'); test_to_str!(u8, 0); test_to_str!(u16, 0); test_to_str!(u32, 0); test_to_str!(u64, 0); test_to_str!(u128, 0); test_to_str!(u8, 10); test_to_str!(u8, 128); test_to_str!(u8, u8::MAX); test_to_str!(u64, 1); test_to_str!(u64, 10); test_to_str!(u64, 42); test_to_str!(u64, u64::MAX); test_to_str!(u128, u128::MAX); test_to_str!(i8, 0); test_to_str!(i16, 0); test_to_str!(i32, 0); test_to_str!(i64, 0); test_to_str!(i128, 0); test_to_str!(i8, -10); test_to_str!(i8, -42); test_to_str!(i8, i8::MAX); test_to_str!(i8, i8::MIN); test_to_str!(i64, 1); test_to_str!(i64, 10); test_to_str!(i64, -42); test_to_str!(i64, i64::MAX); test_to_str!(i64, i64::MIN); test_to_str!(i128, i128::MAX); test_to_str!(i128, i128::MIN); } } const-str-0.7.0/src/__ctfe/trim_ascii.rs000064400000000000000000000136741046102023000162760ustar 00000000000000pub struct TrimAscii(pub T); impl<'a> TrimAscii<&'a str> { pub const fn const_eval(&self) -> &'a str { crate::str::trim_ascii(self.0) } } pub struct TrimAsciiStart(pub T); impl<'a> TrimAsciiStart<&'a str> { pub const fn const_eval(&self) -> &'a str { crate::str::trim_ascii_start(self.0) } } pub struct TrimAsciiEnd(pub T); impl<'a> TrimAsciiEnd<&'a str> { pub const fn const_eval(&self) -> &'a str { crate::str::trim_ascii_end(self.0) } } /// Trims ASCII whitespace from both ends of a string. /// /// ASCII whitespace characters are space (0x20), tab (0x09), newline (0x0A), /// vertical tab (0x0B), form feed (0x0C), and carriage return (0x0D). /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const TRIMMED: &str = const_str::trim_ascii!(" hello world "); /// assert_eq!(TRIMMED, "hello world"); /// /// const MIXED: &str = const_str::trim_ascii!("\t\n hello\tworld\n \r"); /// assert_eq!(MIXED, "hello\tworld"); /// /// const EMPTY: &str = const_str::trim_ascii!(" "); /// assert_eq!(EMPTY, ""); /// /// // Works in const functions too /// const fn process_string(s: &str) -> &str { /// const_str::trim_ascii!(s) /// } /// assert_eq!(process_string(" test "), "test"); /// ``` #[macro_export] macro_rules! trim_ascii { ($s: expr) => { $crate::__ctfe::TrimAscii($s).const_eval() }; } /// Trims ASCII whitespace from the start of a string. /// /// ASCII whitespace characters are space (0x20), tab (0x09), newline (0x0A), /// vertical tab (0x0B), form feed (0x0C), and carriage return (0x0D). /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const TRIMMED: &str = const_str::trim_ascii_start!(" hello world "); /// assert_eq!(TRIMMED, "hello world "); /// /// const MIXED: &str = const_str::trim_ascii_start!("\t\n hello\tworld"); /// assert_eq!(MIXED, "hello\tworld"); /// /// const NO_WHITESPACE: &str = const_str::trim_ascii_start!("hello"); /// assert_eq!(NO_WHITESPACE, "hello"); /// /// // Works in const functions too /// const fn process_string(s: &str) -> &str { /// const_str::trim_ascii_start!(s) /// } /// assert_eq!(process_string(" test"), "test"); /// ``` #[macro_export] macro_rules! trim_ascii_start { ($s: expr) => { $crate::__ctfe::TrimAsciiStart($s).const_eval() }; } /// Trims ASCII whitespace from the end of a string. /// /// ASCII whitespace characters are space (0x20), tab (0x09), newline (0x0A), /// vertical tab (0x0B), form feed (0x0C), and carriage return (0x0D). /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). /// /// # Examples /// /// ``` /// const TRIMMED: &str = const_str::trim_ascii_end!(" hello world "); /// assert_eq!(TRIMMED, " hello world"); /// /// const MIXED: &str = const_str::trim_ascii_end!("hello\tworld\n \r"); /// assert_eq!(MIXED, "hello\tworld"); /// /// const NO_WHITESPACE: &str = const_str::trim_ascii_end!("hello"); /// assert_eq!(NO_WHITESPACE, "hello"); /// /// // Works in const functions too /// const fn process_string(s: &str) -> &str { /// const_str::trim_ascii_end!(s) /// } /// assert_eq!(process_string("test "), "test"); /// ``` #[macro_export] macro_rules! trim_ascii_end { ($s: expr) => { $crate::__ctfe::TrimAsciiEnd($s).const_eval() }; } #[cfg(test)] mod tests { #[test] fn test_trim_ascii() { const S1: &str = trim_ascii!(" hello world "); assert_eq!(S1, "hello world"); const S2: &str = trim_ascii!("\t\n hello\tworld\n \r"); assert_eq!(S2, "hello\tworld"); const S3: &str = trim_ascii!(" "); assert_eq!(S3, ""); const S4: &str = trim_ascii!("hello"); assert_eq!(S4, "hello"); const S5: &str = trim_ascii!(""); assert_eq!(S5, ""); } #[test] fn test_trim_ascii_start() { const S1: &str = trim_ascii_start!(" hello world "); assert_eq!(S1, "hello world "); const S2: &str = trim_ascii_start!("\t\n hello\tworld"); assert_eq!(S2, "hello\tworld"); const S3: &str = trim_ascii_start!("hello"); assert_eq!(S3, "hello"); const S4: &str = trim_ascii_start!(""); assert_eq!(S4, ""); const S5: &str = trim_ascii_start!(" "); assert_eq!(S5, ""); } #[test] fn test_trim_ascii_end() { const S1: &str = trim_ascii_end!(" hello world "); assert_eq!(S1, " hello world"); const S2: &str = trim_ascii_end!("hello\tworld\n \r"); assert_eq!(S2, "hello\tworld"); const S3: &str = trim_ascii_end!("hello"); assert_eq!(S3, "hello"); const S4: &str = trim_ascii_end!(""); assert_eq!(S4, ""); const S5: &str = trim_ascii_end!(" "); assert_eq!(S5, ""); } #[test] fn test_edge_cases() { // Test with all types of ASCII whitespace const ALL_WS: &str = trim_ascii!(" \t\n\x0B\x0C\r"); assert_eq!(ALL_WS, ""); // Test with whitespace in the middle (should be preserved) const MIDDLE_WS: &str = trim_ascii!(" hello world "); assert_eq!(MIDDLE_WS, "hello world"); // Test with only start whitespace const START_WS: &str = trim_ascii!(" hello"); assert_eq!(START_WS, "hello"); // Test with only end whitespace const END_WS: &str = trim_ascii!("hello "); assert_eq!(END_WS, "hello"); } #[test] fn test_const_fn_compatibility() { const fn process_trim(s: &str) -> &str { trim_ascii!(s) } const fn process_trim_start(s: &str) -> &str { trim_ascii_start!(s) } const fn process_trim_end(s: &str) -> &str { trim_ascii_end!(s) } assert_eq!(process_trim(" test "), "test"); assert_eq!(process_trim_start(" test "), "test "); assert_eq!(process_trim_end(" test "), " test"); } } const-str-0.7.0/src/__ctfe/unwrap.rs000064400000000000000000000011651046102023000154570ustar 00000000000000pub struct Unwrap(pub T); impl Unwrap> { pub const fn const_eval(self) -> T { match self.0 { Some(x) => x, None => panic!("called `Option::unwrap()` on a `None` value"), } } } /// Unwraps a container, returns the content. /// /// The input type must be one of /// + [`Option`], where `T: Copy`. /// /// The [`Copy`] bound may be relaxed in the future. /// /// This macro is [const-fn compatible](./index.html#const-fn-compatible). #[macro_export] macro_rules! unwrap { ($expr: expr) => {{ $crate::__ctfe::Unwrap($expr).const_eval() }}; } const-str-0.7.0/src/__proc/case.rs000064400000000000000000000060221046102023000150750ustar 00000000000000pub use const_str_proc_macro::convert_case; macro_rules! convert_case_doc { () => { r#" Converts a string literal to a specified case. These variants require the feature `case`. + lower_camel + upper_camel + title + train + kebab + snake + shouty_snake + shouty_kebab # Examples ``` use const_str::convert_case; const S1: &str = convert_case!(lower, "Lower Case"); const S2: &str = convert_case!(upper, "Upper Case"); # #[cfg(feature = "case")] const S3: &str = convert_case!(lower_camel, "lower camel case"); # #[cfg(feature = "case")] const S4: &str = convert_case!(upper_camel, "upper camel case"); # #[cfg(feature = "case")] const S5: &str = convert_case!(title, "title case"); # #[cfg(feature = "case")] const S6: &str = convert_case!(train, "train case"); # #[cfg(feature = "case")] const S7: &str = convert_case!(snake, "snake case"); # #[cfg(feature = "case")] const S8: &str = convert_case!(kebab, "kebab case"); # #[cfg(feature = "case")] const S9: &str = convert_case!(shouty_snake, "shouty snake case"); # #[cfg(feature = "case")] const S10: &str = convert_case!(shouty_kebab, "shouty kebab case"); assert_eq!(S1, "lower case"); assert_eq!(S2, "UPPER CASE"); # #[cfg(feature = "case")] assert_eq!(S3, "lowerCamelCase"); # #[cfg(feature = "case")] assert_eq!(S4, "UpperCamelCase"); # #[cfg(feature = "case")] assert_eq!(S5, "Title Case"); # #[cfg(feature = "case")] assert_eq!(S6, "Train-Case"); # #[cfg(feature = "case")] assert_eq!(S7, "snake_case"); # #[cfg(feature = "case")] assert_eq!(S8, "kebab-case"); # #[cfg(feature = "case")] assert_eq!(S9, "SHOUTY_SNAKE_CASE"); # #[cfg(feature = "case")] assert_eq!(S10, "SHOUTY-KEBAB-CASE"); ``` "# }; } #[doc = convert_case_doc!() ] // stable since 1.54 #[cfg(not(feature = "case"))] #[macro_export] macro_rules! convert_case { (lower, $s: literal) => { $crate::__proc::convert_case!(lower, $s) }; (upper, $s: literal) => { $crate::__proc::convert_case!(upper, $s) }; } #[cfg_attr(docsrs, doc(cfg(any(feature = "proc", feature = "case"))))] #[doc = convert_case_doc!() ] // stable since 1.54 #[cfg(feature = "case")] #[macro_export] macro_rules! convert_case { (lower, $s: literal) => { $crate::__proc::convert_case!(lower, $s) }; (upper, $s: literal) => { $crate::__proc::convert_case!(upper, $s) }; (lower_camel, $s: literal) => { $crate::__proc::convert_case!(lower_camel, $s) }; (upper_camel, $s: literal) => { $crate::__proc::convert_case!(upper_camel, $s) }; (title, $s: literal) => { $crate::__proc::convert_case!(title, $s) }; (train, $s: literal) => { $crate::__proc::convert_case!(train, $s) }; (snake, $s: literal) => { $crate::__proc::convert_case!(snake, $s) }; (kebab, $s: literal) => { $crate::__proc::convert_case!(kebab, $s) }; (shouty_snake, $s: literal) => { $crate::__proc::convert_case!(shouty_snake, $s) }; (shouty_kebab, $s: literal) => { $crate::__proc::convert_case!(shouty_kebab, $s) }; } const-str-0.7.0/src/__proc/fmt.rs000064400000000000000000000062071046102023000147550ustar 00000000000000pub use const_str_proc_macro::format_parts; /// Creates a string slice using interpolation of const expressions. /// /// The input type must be one of /// /// + [`&str`] /// + [`char`] /// + [`bool`] /// + [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] /// + [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] /// /// # Examples /// /// ``` /// use const_str::format as const_format; /// /// const PROMPT: &str = "The answer is"; /// const ANSWER: usize = 42; /// /// const MESSAGE_1: &str = const_format!("{PROMPT} {ANSWER}"); /// const MESSAGE_2: &str = const_format!("{} {}", PROMPT, ANSWER); /// const MESSAGE_3: &str = const_format!("{0} {1}", PROMPT, ANSWER); /// const MESSAGE_4: &str = const_format!("{a} {b}", a = PROMPT, b = ANSWER); /// const MESSAGE_5: &str = const_format!("{} {a}", PROMPT, a = ANSWER); /// /// assert_eq!(MESSAGE_1, "The answer is 42"); /// assert_eq!(MESSAGE_1, MESSAGE_2); /// assert_eq!(MESSAGE_1, MESSAGE_3); /// assert_eq!(MESSAGE_1, MESSAGE_4); /// assert_eq!(MESSAGE_1, MESSAGE_5); /// ``` /// #[cfg_attr(docsrs, doc(cfg(feature = "proc")))] #[macro_export] macro_rules! format { ($fmt: literal $($args:tt)*) => {{ use ::core::primitive::{str, usize}; use $crate::__ctfe::FmtSpec; #[allow(unused_imports)] use $crate::{__fmt_debug, __fmt_display, __fmt_lowerhex, __fmt_upperhex, __fmt_binary}; const STRS: &[&str] = $crate::__proc::format_parts!($fmt $($args)*); const OUTPUT_LEN: usize = $crate::__ctfe::Concat(STRS).output_len(); const OUTPUT_BUF: $crate::__ctfe::StrBuf = $crate::__ctfe::Concat(STRS).const_eval(); OUTPUT_BUF.as_str() }}; } #[cfg(test)] mod tests { #[allow(clippy::uninlined_format_args)] #[test] fn test_const_format() { use crate::format as const_format; { const A: usize = 1; const X: &str = const_format!("{:?}", A); let ans = std::format!("{:?}", A); assert_eq!(X, ans); } { const A: bool = true; const B: bool = false; const X: &str = const_format!("{1:?} {0:?} {:?}", A, B); let ans = std::format!("{1:?} {0:?} {:?}", A, B); assert_eq!(X, ans); } { const A: char = '我'; const X: &str = const_format!("{a:?} {0}", A, a = A); let ans = std::format!("{a:?} {0}", A, a = A); assert_eq!(X, ans); } { const A: &str = "团长\0\t\r\n\"'and希望之花"; const X: &str = const_format!("{:?}", A); let ans = format!("{:?}", A); assert_eq!(X, ans) } { const A: u32 = 42; const X: &str = const_format!("{0:x} {0:X} {0:#x} {0:#X} {0:b} {0:#b}", A); let ans = std::format!("{0:x} {0:X} {0:#x} {0:#X} {0:b} {0:#b}", A); assert_eq!(X, ans) } { const A: i32 = -42; const X: &str = const_format!("{A:x} {A:X} {A:#x} {A:#X} {A:b} {A:#b}"); let ans = std::format!("{0:x} {0:X} {0:#x} {0:#X} {0:b} {0:#b}", A); assert_eq!(X, ans) } } } const-str-0.7.0/src/__proc/http.rs000064400000000000000000000010051046102023000151350ustar 00000000000000pub use const_str_proc_macro::verified_header_name; /// Returns a compile-time verified header name string literal. /// /// # Examples /// /// ``` /// use http::header::HeaderName; /// let name = const_str::verified_header_name!("content-md5"); /// assert_eq!(HeaderName::from_static(name).as_str(), "content-md5"); /// ``` /// #[cfg_attr(docsrs, doc(cfg(feature = "http")))] #[macro_export] macro_rules! verified_header_name { ($name: literal) => { $crate::__proc::verified_header_name!($name) }; } const-str-0.7.0/src/__proc/regex.rs000064400000000000000000000015061046102023000152760ustar 00000000000000pub use const_str_proc_macro::{regex_assert_match, verified_regex}; /// Returns a compile-time verified regex string literal. /// /// # Examples /// /// ``` /// use regex::Regex; /// let re = const_str::verified_regex!(r"^\d{4}-\d{2}-\d{2}$"); /// assert!(Regex::new(re).is_ok()); /// ``` /// #[cfg_attr(docsrs, doc(cfg(feature = "regex")))] #[macro_export] macro_rules! verified_regex { ($re: literal) => { $crate::__proc::verified_regex!($re) }; } /// Asserts that the string literal matches the pattern. /// /// # Examples /// ``` /// const_str::regex_assert_match!(r"^\d{4}-\d{2}-\d{2}$", "2014-01-01"); /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "regex")))] #[macro_export] macro_rules! regex_assert_match { ($re: literal, $text: literal) => { $crate::__proc::regex_assert_match!($re, $text) }; } const-str-0.7.0/src/ascii.rs000064400000000000000000000004761046102023000140200ustar 00000000000000pub const fn num_to_hex_digit(x: u8) -> u8 { match x { 0..=9 => b'0' + x, 10..=15 => b'a' + (x - 10), _ => panic!("invalid hex number"), } } pub const fn num_from_dec_digit(d: u8) -> u8 { match d { b'0'..=b'9' => d - b'0', _ => panic!("invalid dec digit"), } } const-str-0.7.0/src/bytes.rs000064400000000000000000000113741046102023000140550ustar 00000000000000use crate::slice::subslice; use core::cmp::Ordering; pub const fn clone(bytes: &[u8]) -> [u8; N] { assert!(bytes.len() == N); let mut buf = [0; N]; let mut i = 0; while i < bytes.len() { buf[i] = bytes[i]; i += 1; } buf } pub const fn equal(lhs: &[u8], rhs: &[u8]) -> bool { if lhs.len() != rhs.len() { return false; } let mut i = 0; while i < lhs.len() { if lhs[i] != rhs[i] { return false; } i += 1; } true } pub const fn compare(lhs: &[u8], rhs: &[u8]) -> Ordering { let lhs_len = lhs.len(); let rhs_len = rhs.len(); let min_len = if lhs_len < rhs_len { lhs_len } else { rhs_len }; let mut i = 0; while i < min_len { if lhs[i] < rhs[i] { return Ordering::Less; } if lhs[i] > rhs[i] { return Ordering::Greater; } i += 1; } if lhs_len < rhs_len { Ordering::Less } else if lhs_len > rhs_len { Ordering::Greater } else { Ordering::Equal } } pub const fn merge(mut buf: [u8; N], bytes: &[u8]) -> [u8; N] { assert!(N <= bytes.len()); let mut i = 0; while i < bytes.len() { buf[i] = bytes[i]; i += 1; } buf } pub const fn reversed(mut arr: [u8; N]) -> [u8; N] { let mut i = 0; while i * 2 < N { let a = arr[i]; let b = arr[N - 1 - i]; arr[i] = b; arr[N - 1 - i] = a; i += 1; } arr } pub const fn contains(haystack: &[u8], needle: &[u8]) -> bool { let haystack_len = haystack.len(); let needle_len = needle.len(); let mut i = 0; while i < haystack_len { let mut j = 0; while j < needle_len && i + j < haystack_len { if haystack[i + j] != needle[j] { break; } j += 1; } if j == needle_len { return true; } i += 1; } false } pub const fn starts_with(haystack: &[u8], needle: &[u8]) -> bool { let haystack_len = haystack.len(); let needle_len = needle.len(); if needle_len > haystack_len { return false; } let mut i = 0; while i < needle_len { if haystack[i] != needle[i] { break; } i += 1 } i == needle_len } pub const fn ends_with(haystack: &[u8], needle: &[u8]) -> bool { let haystack_len = haystack.len(); let needle_len = needle.len(); if needle_len > haystack_len { return false; } let mut i = 0; while i < needle_len { if haystack[haystack_len - needle_len + i] != needle[i] { break; } i += 1 } i == needle_len } pub const fn strip_prefix<'s>(s: &'s [u8], prefix: &[u8]) -> Option<&'s [u8]> { if starts_with(s, prefix) { Some(subslice(s, prefix.len()..s.len())) } else { None } } pub const fn strip_suffix<'s>(s: &'s [u8], suffix: &[u8]) -> Option<&'s [u8]> { if ends_with(s, suffix) { Some(subslice(s, 0..s.len() - suffix.len())) } else { None } } #[cfg(test)] mod tests { use super::*; #[test] fn test_reversed() { let arr = [0, 1]; assert_eq!(reversed(arr), [1, 0]); let arr = [0, 1, 2]; assert_eq!(reversed(arr), [2, 1, 0]); } #[test] fn test_contains() { macro_rules! test_contains { (true, $haystack: expr, $needle: expr) => { assert!(contains($haystack.as_ref(), $needle.as_ref())); }; (false, $haystack: expr, $needle: expr) => { assert!(!contains($haystack.as_ref(), $needle.as_ref())); }; } let buf = b"abcdefgh"; test_contains!(true, buf, b""); test_contains!(true, buf, b"a"); test_contains!(true, buf, b"ef"); test_contains!(false, buf, b"xyz"); test_contains!(true, "asd", ""); test_contains!(true, "asd", "a"); test_contains!(true, "asdf", "sd"); test_contains!(false, "", "a"); test_contains!(false, "asd", "abcd"); test_contains!(true, "唐可可", "可"); test_contains!(true, "Liyuu", "i"); test_contains!(false, "Liyuu", "我"); } #[test] fn test_starts_with() { assert!(starts_with(b"", b"")); assert!(starts_with(b"a", b"")); assert!(starts_with(b"a", b"a")); assert!(!starts_with(b"", b"a")); assert!(!starts_with(b"ba", b"a")); } #[test] fn test_ends_with() { assert!(ends_with(b"", b"")); assert!(ends_with(b"a", b"")); assert!(ends_with(b"a", b"a")); assert!(!ends_with(b"", b"a")); assert!(!ends_with(b"ab", b"a")); } } const-str-0.7.0/src/lib.rs000064400000000000000000000115051046102023000134710ustar 00000000000000//! Compile-time string operations. //! See the [macro list](#macros) for what you need. //! //! ## MSRV history //! //! Current: Rust 1.77.0 //! //! - `v0.6.0`: Rust 1.77.0 //! - `v0.5.7`: Rust 1.65.0 //! - `v0.5.0`: Rust 1.64.0 //! - `v0.4.0`: Rust 1.61.0 //! //! ## Troubleshoot //! //! You don't have to care about this section //! unless you come across some compile errors about const evaluation. //! //! ```txt //! error[E0435]: attempt to use a non-constant value in a constant //! ``` //! //! There are mainly two kinds of macros in this crate, //! which have different requirements for the arguments. //! - [const-context only](#const-context-only) //! - [const-fn compatible](#const-fn-compatible) //! //! ### const-context only //! //! These macros can only be used in [const contexts][const-context]. //! The expanded code is equivalent to compute new [constant items][const-item]. //! It implies that the *arguments* of these macros must be constant values, //! similar to [`consteval`][consteval] in C++ world. //! //! The following examples will not work: //! ```compile_fail //! const fn foo(a: &str, b: &str) -> &str { //! const_str::concat!(a, b) //! } //! ``` //! ```compile_fail //! const C: &str = { //! let a = "Hello"; //! let b = "World"; //! const_str::concat!(a, b); //! }; //! ``` //! //! Instead, this way will work: //! ``` //! const A: &str = "Hello"; //! const B: &str = "World"; //! const C: &str = const_str::concat!(A, " ", B); //! assert_eq!(C, "Hello World"); //! ``` //! //! ### const-fn compatible //! //! These macros can be used in [const contexts][const-context] and [const functions][const-fn]. //! The expanded code is equivalent to calling const functions. //! It implies that the *arguments* of these macros can be any expressions, //! similar to [`constexpr`][constexpr] in C++ world. //! //! ``` //! const fn calc(y: &str, m: &str, d: &str) -> u64 { //! let y = const_str::parse!(y, u64); //! let m = const_str::parse!(m, u64); //! let d = const_str::parse!(d, u64); //! (y * 10000 + m * 100 + d) //! } //! const TIME: u64 = calc("2025", "01", "26"); //! assert_eq!(TIME, 20250126); //! ``` //! //! You can also use these macros in normal functions, //! but they may be much slower than the runtime equivalents. //! It's recommended to use them only if you need compile-time evaluation. //! //! [const-context]: https://doc.rust-lang.org/reference/const_eval.html#const-context //! [const-fn]: https://doc.rust-lang.org/reference/const_eval.html#const-functions //! [const-item]: https://doc.rust-lang.org/reference/items/constant-items.html //! [consteval]: https://en.cppreference.com/w/cpp/language/consteval //! [constexpr]: https://en.cppreference.com/w/cpp/language/constexpr //! #![deny(unsafe_code, missing_docs, clippy::all, clippy::cargo)] #![allow( clippy::missing_docs_in_private_items, clippy::missing_inline_in_public_items, clippy::implicit_return )] #![cfg_attr(not(any(test, feature = "std")), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(feature = "unstable", allow(clippy::incompatible_msrv))] #[allow(unused_macros)] macro_rules! cfg_group { ($($item:item)*) => { $($item)* } } mod ascii; mod bytes; mod printable; mod slice; mod str; mod utf16; mod utf8; #[doc(hidden)] #[cfg(feature = "proc")] pub mod __proc { mod case; pub use self::case::*; mod fmt; pub use self::fmt::*; #[cfg(feature = "http")] cfg_group! { mod http; pub use self::http::*; } #[cfg(feature = "regex")] cfg_group! { mod regex; pub use self::regex::*; } } #[doc(hidden)] pub mod __ctfe { mod ascii_case; pub use self::ascii_case::*; mod chain; // pub use self::chain::*; mod compare; pub use self::compare::*; mod concat; pub use self::concat::*; mod concat_bytes; pub use self::concat_bytes::*; mod cstr; pub use self::cstr::*; mod encode; pub use self::encode::*; mod equal; pub use self::equal::*; mod find; pub use self::find::*; mod fmt; pub use self::fmt::*; mod hex; pub use self::hex::*; mod net; pub use self::net::*; mod parse; pub use self::parse::*; mod repeat; pub use self::repeat::*; mod replace; pub use self::replace::*; mod str; pub use self::str::*; mod to_byte_array; pub use self::to_byte_array::*; mod to_char_array; pub use self::to_char_array::*; mod to_str; pub use self::to_str::*; mod sorted; pub use self::sorted::*; mod split; pub use self::split::*; mod squish; pub use self::squish::*; mod is_ascii; pub use self::is_ascii::*; mod eq_ignore_ascii_case; pub use self::eq_ignore_ascii_case::*; mod unwrap; pub use self::unwrap::*; mod trim_ascii; pub use self::trim_ascii::*; } const-str-0.7.0/src/printable.rs000064400000000000000000000327101046102023000147040ustar 00000000000000#![allow(clippy::comparison_chain)] #![allow(clippy::manual_range_contains)] // Edited from const fn check( x: u16, singletonuppers: &[(u8, u8)], singletonlowers: &[u8], normal: &[u8], ) -> bool { let xupper = (x >> 8) as u8; let mut lowerstart = 0; // for &(upper, lowercount) in singletonuppers let mut i = 0; while i < singletonuppers.len() { let (upper, lowercount) = singletonuppers[i]; let lowerend = lowerstart + lowercount as usize; if xupper == upper { // for &lower in &singletonlowers[lowerstart..lowerend] let mut j = lowerstart; while j < lowerend { let lower = singletonlowers[j]; if lower == x as u8 { return false; } j += 1; } // end for } else if xupper < upper { break; } lowerstart = lowerend; i += 1; } // end for let mut i = 0; macro_rules! normal_next { () => {{ let v = normal[i]; i += 1; v }}; } let mut x = x as i32; let mut current = true; while i < normal.len() { let v = normal_next!(); let len = if v & 0x80 != 0 { (((v & 0x7f) as i32) << 8) | normal_next!() as i32 } else { v as i32 }; x -= len; if x < 0 { break; } current = !current; } current } pub const fn is_printable(x: char) -> bool { let x = x as u32; let lower = x as u16; if x < 32 { // ASCII fast path false } else if x < 127 { // ASCII fast path true } else if x < 0x10000 { check(lower, SINGLETONS0U, SINGLETONS0L, NORMAL0) } else if x < 0x20000 { check(lower, SINGLETONS1U, SINGLETONS1L, NORMAL1) } else { if 0x2a6e0 <= x && x < 0x2a700 { return false; } if 0x2b73a <= x && x < 0x2b740 { return false; } if 0x2b81e <= x && x < 0x2b820 { return false; } if 0x2cea2 <= x && x < 0x2ceb0 { return false; } if 0x2ebe1 <= x && x < 0x2ebf0 { return false; } if 0x2ee5e <= x && x < 0x2f800 { return false; } if 0x2fa1e <= x && x < 0x30000 { return false; } if 0x3134b <= x && x < 0x31350 { return false; } if 0x323b0 <= x && x < 0xe0100 { return false; } if 0xe01f0 <= x && x < 0x110000 { return false; } true } } #[rustfmt::skip] const SINGLETONS0U: &[(u8, u8)] = &[ (0x00, 1), (0x03, 5), (0x05, 6), (0x06, 2), (0x07, 6), (0x08, 7), (0x09, 17), (0x0a, 28), (0x0b, 25), (0x0c, 26), (0x0d, 16), (0x0e, 12), (0x0f, 4), (0x10, 3), (0x12, 18), (0x13, 9), (0x16, 1), (0x17, 4), (0x18, 1), (0x19, 3), (0x1a, 7), (0x1b, 1), (0x1c, 2), (0x1f, 22), (0x20, 3), (0x2b, 3), (0x2d, 11), (0x2e, 1), (0x30, 4), (0x31, 2), (0x32, 1), (0xa7, 4), (0xa9, 2), (0xaa, 4), (0xab, 8), (0xfa, 2), (0xfb, 5), (0xfd, 2), (0xfe, 3), (0xff, 9), ]; #[rustfmt::skip] const SINGLETONS0L: &[u8] = &[ 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, 0x1c, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, 0x5c, 0x5d, 0x5f, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x3a, 0x3b, 0x45, 0x49, 0x57, 0x5b, 0x5c, 0x5e, 0x5f, 0x64, 0x65, 0x8d, 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, 0xfe, 0xff, 0x80, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x1f, 0x6e, 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0x4d, 0xbb, 0xbc, 0x16, 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, 0x74, 0x75, 0x96, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x00, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xce, 0xcf, 0xd2, 0xd4, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, 0xfe, 0xff, ]; #[rustfmt::skip] const SINGLETONS1U: &[(u8, u8)] = &[ (0x00, 6), (0x01, 1), (0x03, 1), (0x04, 2), (0x05, 7), (0x07, 2), (0x08, 8), (0x09, 2), (0x0a, 5), (0x0b, 2), (0x0e, 4), (0x10, 1), (0x11, 2), (0x12, 5), (0x13, 28), (0x14, 1), (0x15, 2), (0x17, 2), (0x19, 13), (0x1c, 5), (0x1d, 8), (0x1f, 1), (0x24, 1), (0x6a, 4), (0x6b, 2), (0xaf, 3), (0xb1, 2), (0xbc, 2), (0xcf, 2), (0xd1, 2), (0xd4, 12), (0xd5, 9), (0xd6, 2), (0xd7, 2), (0xda, 1), (0xe0, 5), (0xe1, 2), (0xe7, 4), (0xe8, 2), (0xee, 32), (0xf0, 4), (0xf8, 2), (0xfa, 4), (0xfb, 1), ]; #[rustfmt::skip] const SINGLETONS1L: &[u8] = &[ 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x7b, 0x8b, 0x93, 0x96, 0xa2, 0xb2, 0xba, 0x86, 0xb1, 0x06, 0x07, 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x8a, 0x8c, 0x8d, 0x8f, 0xb6, 0xc1, 0xc3, 0xc4, 0xc6, 0xcb, 0xd6, 0x5c, 0xb6, 0xb7, 0x1b, 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, 0x69, 0x8f, 0x92, 0x11, 0x6f, 0x5f, 0xbf, 0xee, 0xef, 0x5a, 0x62, 0xf4, 0xfc, 0xff, 0x53, 0x54, 0x9a, 0x9b, 0x2e, 0x2f, 0x27, 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xe7, 0xec, 0xef, 0xff, 0xc5, 0xc6, 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, 0xae, 0xaf, 0x6e, 0x6f, 0xdd, 0xde, 0x93, ]; #[rustfmt::skip] const NORMAL0: &[u8] = &[ 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x05, 0x1f, 0x08, 0x81, 0x1c, 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x4e, 0x07, 0x1b, 0x07, 0x57, 0x07, 0x02, 0x06, 0x17, 0x0c, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, 0x16, 0x09, 0x18, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, 0x06, 0x0a, 0x06, 0x2f, 0x31, 0x80, 0xf4, 0x08, 0x3c, 0x03, 0x0f, 0x03, 0x3e, 0x05, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x21, 0x0f, 0x21, 0x0f, 0x80, 0x8c, 0x04, 0x82, 0x9a, 0x16, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xbe, 0x22, 0x74, 0x0c, 0x80, 0xd6, 0x1a, 0x81, 0x10, 0x05, 0x80, 0xe1, 0x09, 0xf2, 0x9e, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, 0xdd, 0x15, 0x3b, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, 0xa6, 0x10, 0x81, 0xf5, 0x07, 0x01, 0x20, 0x2a, 0x06, 0x4c, 0x04, 0x80, 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, ]; #[rustfmt::skip] const NORMAL1: &[u8] = &[ 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x4e, 0x03, 0x34, 0x0c, 0x81, 0x37, 0x09, 0x16, 0x0a, 0x08, 0x18, 0x3b, 0x45, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x06, 0x26, 0x03, 0x1d, 0x08, 0x02, 0x80, 0xd0, 0x52, 0x10, 0x03, 0x37, 0x2c, 0x08, 0x2a, 0x16, 0x1a, 0x26, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x24, 0x09, 0x44, 0x0d, 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x42, 0x3e, 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, 0x05, 0x0b, 0x59, 0x08, 0x02, 0x1d, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x3a, 0x06, 0x0a, 0x06, 0x14, 0x1c, 0x2c, 0x04, 0x17, 0x80, 0xb9, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, 0x1b, 0x48, 0x08, 0x53, 0x0d, 0x49, 0x07, 0x0a, 0x80, 0xb6, 0x22, 0x0e, 0x0a, 0x06, 0x46, 0x0a, 0x1d, 0x03, 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, 0x36, 0x19, 0x07, 0x3b, 0x03, 0x1d, 0x55, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, 0x0b, 0x80, 0xc4, 0x8a, 0x4c, 0x63, 0x0d, 0x84, 0x30, 0x10, 0x16, 0x0a, 0x8f, 0x9b, 0x05, 0x82, 0x47, 0x9a, 0xb9, 0x3a, 0x86, 0xc6, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x5c, 0x06, 0x26, 0x0a, 0x46, 0x0a, 0x28, 0x05, 0x13, 0x81, 0xb0, 0x3a, 0x80, 0xc6, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x29, 0x0a, 0xa2, 0xe7, 0x81, 0x33, 0x0f, 0x01, 0x1d, 0x06, 0x0e, 0x04, 0x08, 0x81, 0x8c, 0x89, 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x8f, 0x60, 0x80, 0xfa, 0x06, 0x81, 0xb4, 0x4c, 0x47, 0x09, 0x74, 0x3c, 0x80, 0xf6, 0x0a, 0x73, 0x08, 0x70, 0x15, 0x46, 0x7a, 0x14, 0x0c, 0x14, 0x0c, 0x57, 0x09, 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x84, 0x50, 0x1f, 0x06, 0x06, 0x80, 0xd5, 0x2b, 0x05, 0x3e, 0x21, 0x01, 0x70, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x40, 0x1f, 0x11, 0x3a, 0x05, 0x01, 0x81, 0xd0, 0x2a, 0x80, 0xd6, 0x2b, 0x04, 0x01, 0x81, 0xe0, 0x80, 0xf7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, 0x9a, 0x83, 0xd8, 0x04, 0x11, 0x03, 0x0d, 0x03, 0x77, 0x04, 0x5f, 0x06, 0x0c, 0x04, 0x01, 0x0f, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x2c, 0x04, 0x02, 0x3e, 0x81, 0x54, 0x0c, 0x1d, 0x03, 0x0a, 0x05, 0x38, 0x07, 0x1c, 0x06, 0x09, 0x07, 0x80, 0xfa, 0x84, 0x06, ]; const-str-0.7.0/src/slice.rs000064400000000000000000000015761046102023000140310ustar 00000000000000#![allow(unsafe_code)] use core::ops::Range; use core::slice; pub const fn advance(s: &[T], count: usize) -> &[T] { let len = s.len(); assert!(count <= len); let base = s.as_ptr(); unsafe { slice::from_raw_parts(base.add(count), len - count) } } pub const fn subslice(s: &[T], range: Range) -> &[T] { let len = s.len(); assert!(range.start <= range.end && range.end <= len); let base = s.as_ptr(); unsafe { slice::from_raw_parts(base.add(range.start), range.end - range.start) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_subslice() { let buf = b"abcdefgh"; assert_eq!(subslice(buf, 0..0), &[]); assert_eq!(subslice(buf, 0..1), b"a"); assert_eq!(subslice(buf, 1..3), b"bc"); assert_eq!(subslice(buf, 7..8), b"h"); assert_eq!(subslice::(&[], 0..0), &[]); } } const-str-0.7.0/src/str.rs000064400000000000000000000121411046102023000135300ustar 00000000000000#![allow(unsafe_code)] use core::cmp::Ordering; use crate::slice::advance; pub const fn equal(lhs: &str, rhs: &str) -> bool { crate::bytes::equal(lhs.as_bytes(), rhs.as_bytes()) } pub const fn compare(lhs: &str, rhs: &str) -> Ordering { crate::bytes::compare(lhs.as_bytes(), rhs.as_bytes()) } pub const unsafe fn char_from_u32(x: u32) -> char { #[cfg(not(feature = "unstable"))] #[allow(unnecessary_transmutes)] { core::mem::transmute(x) } #[cfg(feature = "unstable")] // feature(const_char_from_u32_unchecked) { core::char::from_u32_unchecked(x) } } pub const fn contains(haystack: &str, needle: &str) -> bool { crate::bytes::contains(haystack.as_bytes(), needle.as_bytes()) } pub const fn starts_with(haystack: &str, needle: &str) -> bool { crate::bytes::starts_with(haystack.as_bytes(), needle.as_bytes()) } pub const fn ends_with(haystack: &str, needle: &str) -> bool { crate::bytes::ends_with(haystack.as_bytes(), needle.as_bytes()) } pub const fn strip_prefix<'s>(s: &'s str, prefix: &str) -> Option<&'s str> { match crate::bytes::strip_prefix(s.as_bytes(), prefix.as_bytes()) { Some(remain) => Some(unsafe { core::str::from_utf8_unchecked(remain) }), None => None, } } pub const fn strip_suffix<'s>(s: &'s str, suffix: &str) -> Option<&'s str> { match crate::bytes::strip_suffix(s.as_bytes(), suffix.as_bytes()) { Some(remain) => Some(unsafe { core::str::from_utf8_unchecked(remain) }), None => None, } } pub const fn next_match<'h>(haystack: &'h str, needle: &str) -> Option<(usize, &'h str)> { assert!(!needle.is_empty()); let lhs = haystack.as_bytes(); let rhs = needle.as_bytes(); let mut i = 0; while i + rhs.len() <= lhs.len() { let mut j = 0; while j < rhs.len() { if lhs[i + j] != rhs[j] { break; } j += 1; } if j == rhs.len() { let remain = advance(lhs, i + rhs.len()); let remain = unsafe { core::str::from_utf8_unchecked(remain) }; return Some((i, remain)); } i += 1; } None } /// Returns true if the byte is an ASCII whitespace character. /// ASCII whitespace: space (0x20), tab (0x09), newline (0x0A), /// vertical tab (0x0B), form feed (0x0C), carriage return (0x0D). const fn is_ascii_whitespace(b: u8) -> bool { matches!(b, b' ' | b'\t' | b'\n' | b'\x0B' | b'\x0C' | b'\r') } /// Trims ASCII whitespace from both ends of a string slice. pub const fn trim_ascii(s: &str) -> &str { let bytes = s.as_bytes(); let len = bytes.len(); // Find start let mut start = 0; while start < len && is_ascii_whitespace(bytes[start]) { start += 1; } // Find end let mut end = len; while end > start && is_ascii_whitespace(bytes[end - 1]) { end -= 1; } let trimmed_bytes = crate::slice::subslice(bytes, start..end); unsafe { core::str::from_utf8_unchecked(trimmed_bytes) } } /// Trims ASCII whitespace from the start of a string slice. pub const fn trim_ascii_start(s: &str) -> &str { let bytes = s.as_bytes(); let len = bytes.len(); // Find start let mut start = 0; while start < len && is_ascii_whitespace(bytes[start]) { start += 1; } let trimmed_bytes = crate::slice::advance(bytes, start); unsafe { core::str::from_utf8_unchecked(trimmed_bytes) } } /// Trims ASCII whitespace from the end of a string slice. pub const fn trim_ascii_end(s: &str) -> &str { let bytes = s.as_bytes(); let len = bytes.len(); // Find end let mut end = len; while end > 0 && is_ascii_whitespace(bytes[end - 1]) { end -= 1; } let trimmed_bytes = crate::slice::subslice(bytes, 0..end); unsafe { core::str::from_utf8_unchecked(trimmed_bytes) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_next_match() { assert_eq!(next_match("abc", "ab"), Some((0, "c"))); assert_eq!(next_match("abc", "bc"), Some((1, ""))); assert_eq!(next_match("abc", "c"), Some((2, ""))); assert_eq!(next_match("abc", "d"), None); } #[test] fn test_trim_ascii() { assert_eq!(trim_ascii(" hello world "), "hello world"); assert_eq!(trim_ascii("\t\n hello\tworld\n \r"), "hello\tworld"); assert_eq!(trim_ascii(" "), ""); assert_eq!(trim_ascii("hello"), "hello"); assert_eq!(trim_ascii(""), ""); } #[test] fn test_trim_ascii_start() { assert_eq!(trim_ascii_start(" hello world "), "hello world "); assert_eq!(trim_ascii_start("\t\n hello\tworld"), "hello\tworld"); assert_eq!(trim_ascii_start("hello"), "hello"); assert_eq!(trim_ascii_start(""), ""); assert_eq!(trim_ascii_start(" "), ""); } #[test] fn test_trim_ascii_end() { assert_eq!(trim_ascii_end(" hello world "), " hello world"); assert_eq!(trim_ascii_end("hello\tworld\n \r"), "hello\tworld"); assert_eq!(trim_ascii_end("hello"), "hello"); assert_eq!(trim_ascii_end(""), ""); assert_eq!(trim_ascii_end(" "), ""); } } const-str-0.7.0/src/utf16.rs000064400000000000000000000021231046102023000136640ustar 00000000000000use crate::slice::advance; pub struct CharEncodeUtf16 { buf: [u16; 2], } impl CharEncodeUtf16 { /// Copied from [char::encode_utf16](https://github.com/rust-lang/rust/blob/0273e3bce7a0ce49e96a9662163e2380cb87e0be/library/core/src/char/methods.rs#L1647-L1682) pub const fn new(ch: char) -> Self { let mut code = ch as u32; let mut buf = [0; 2]; if (code & 0xFFFF) == code { buf[0] = code as u16; } else { code -= 0x1_0000; buf[0] = 0xD800 | ((code >> 10) as u16); buf[1] = 0xDC00 | ((code as u16) & 0x3FF); } Self { buf } } pub const fn has_second(&self) -> bool { self.buf[1] != 0 } pub const fn first(&self) -> u16 { self.buf[0] } pub const fn second(&self) -> u16 { self.buf[1] } } pub const fn str_len_utf16(s: &str) -> usize { let mut s = s.as_bytes(); let mut ans = 0; while let Some((ch, count)) = crate::utf8::next_char(s) { s = advance(s, count); ans += ch.len_utf16(); // const since 1.52 } ans } const-str-0.7.0/src/utf8.rs000064400000000000000000000217771046102023000136250ustar 00000000000000#![allow(unsafe_code)] use crate::printable::is_printable; use crate::slice::advance; use crate::slice::subslice; pub struct CharEncodeUtf8 { buf: [u8; 4], len: u8, } impl CharEncodeUtf8 { /// Copied from [char::encode_utf8](https://github.com/rust-lang/rust/blob/0273e3bce7a0ce49e96a9662163e2380cb87e0be/library/core/src/char/methods.rs#L1600-L1645) pub const fn new(ch: char) -> Self { // UTF-8 ranges and tags for encoding characters const TAG_CONT: u8 = 0b1000_0000; const TAG_TWO_B: u8 = 0b1100_0000; const TAG_THREE_B: u8 = 0b1110_0000; const TAG_FOUR_B: u8 = 0b1111_0000; let mut buf = [0; 4]; let len = ch.len_utf8(); let code = ch as u32; match len { 1 => { buf[0] = code as u8; } 2 => { buf[0] = ((code >> 6) & 0x1F) as u8 | TAG_TWO_B; buf[1] = (code & 0x3F) as u8 | TAG_CONT; } 3 => { buf[0] = ((code >> 12) & 0x0F) as u8 | TAG_THREE_B; buf[1] = ((code >> 6) & 0x3F) as u8 | TAG_CONT; buf[2] = (code & 0x3F) as u8 | TAG_CONT; } 4 => { buf[0] = ((code >> 18) & 0x07) as u8 | TAG_FOUR_B; buf[1] = ((code >> 12) & 0x3F) as u8 | TAG_CONT; buf[2] = ((code >> 6) & 0x3F) as u8 | TAG_CONT; buf[3] = (code & 0x3F) as u8 | TAG_CONT; } _ => {} }; CharEncodeUtf8 { buf, len: len as u8, } } pub const fn as_bytes(&self) -> &[u8] { subslice(&self.buf, 0..self.len as usize) } // const since 1.55 pub const fn as_str(&self) -> &str { unsafe { core::str::from_utf8_unchecked(self.as_bytes()) } } } pub struct CharEscapeUnicode { buf: [u8; 10], len: u8, } impl CharEscapeUnicode { const unsafe fn from_code_point(code: u32) -> Self { let mut hex_buf = [0; 10]; let mut hex_pos = 0; let mut x = code; loop { hex_buf[hex_pos] = crate::ascii::num_to_hex_digit((x as u8) & 0x0f); hex_pos += 1; x >>= 4; if x == 0 { break; } } let mut buf = [b'\\', b'u', b'{', 0, 0, 0, 0, 0, 0, 0]; let mut pos = 3; while hex_pos > 0 { hex_pos -= 1; buf[pos] = hex_buf[hex_pos]; pos += 1; } buf[pos] = b'}'; pos += 1; Self { buf, len: pos as u8, } } pub const fn new(ch: char) -> Self { unsafe { Self::from_code_point(ch as u32) } } #[cfg(test)] pub fn as_str(&self) -> &str { unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len as usize]) } } } pub struct CharEscapeDebug { buf: [u8; 10], len: u8, } pub struct CharEscapeDebugArgs { pub escape_single_quote: bool, pub escape_double_quote: bool, } impl CharEscapeDebugArgs { #[cfg(test)] pub const ESCAPE_ALL: Self = Self { escape_single_quote: true, escape_double_quote: true, }; } impl CharEscapeDebug { pub const fn new(ch: char, args: CharEscapeDebugArgs) -> Self { match ch { '\0' => Self::backslash_ascii(b'0'), '\t' => Self::backslash_ascii(b't'), '\r' => Self::backslash_ascii(b'r'), '\n' => Self::backslash_ascii(b'n'), '\\' => Self::backslash_ascii(b'\\'), '"' if args.escape_double_quote => Self::backslash_ascii(b'"'), '\'' if args.escape_single_quote => Self::backslash_ascii(b'\''), _ if is_printable(ch) => Self::printable(ch), _ => Self::unicode(ch), } } const fn printable(ch: char) -> Self { let e = CharEncodeUtf8::new(ch); Self { buf: [e.buf[0], e.buf[1], e.buf[2], e.buf[3], 0, 0, 0, 0, 0, 0], len: e.len, } } const fn backslash_ascii(ch: u8) -> Self { Self { buf: [b'\\', ch, 0, 0, 0, 0, 0, 0, 0, 0], len: 2, } } const fn unicode(ch: char) -> Self { let e = CharEscapeUnicode::new(ch); Self { buf: e.buf, len: e.len, } } pub const fn as_bytes(&self) -> &[u8] { subslice(&self.buf, 0..self.len as usize) } #[cfg(test)] pub fn as_str(&self) -> &str { unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len as usize]) } } // pub const fn to_str_buf(&self) -> StrBuf { // let buf = crate::bytes::clone(self.as_bytes()); // unsafe { StrBuf::new_unchecked(buf) } // } } pub const fn next_char(bytes: &[u8]) -> Option<(char, usize)> { /// Copied from [core::str::validations](https://github.com/rust-lang/rust/blob/e7958d35ca2c898a223efe402481e0ecb854310a/library/core/src/str/validations.rs#L7-L68) #[allow(clippy::many_single_char_names)] const fn next_code_point(bytes: &[u8]) -> Option<(u32, usize)> { const CONT_MASK: u8 = 0b0011_1111; const fn utf8_first_byte(byte: u8, width: u32) -> u32 { (byte & (0x7F >> width)) as u32 } const fn utf8_acc_cont_byte(ch: u32, byte: u8) -> u32 { (ch << 6) | (byte & CONT_MASK) as u32 } #[allow(clippy::manual_unwrap_or_default)] // FIXME const fn unwrap_or_0(opt: Option) -> u8 { match opt { Some(byte) => byte, None => 0, } } let mut i = 0; macro_rules! next { () => {{ if i < bytes.len() { let x = Some(bytes[i]); i += 1; x } else { None } }}; } let x = match next!() { Some(x) => x, None => return None, }; if x < 128 { return Some((x as u32, i)); } let init = utf8_first_byte(x, 2); let y = unwrap_or_0(next!()); let mut ch = utf8_acc_cont_byte(init, y); if x >= 0xE0 { let z = unwrap_or_0(next!()); let y_z = utf8_acc_cont_byte((y & CONT_MASK) as u32, z); ch = (init << 12) | y_z; if x >= 0xF0 { let w = unwrap_or_0(next!()); ch = ((init & 7) << 18) | utf8_acc_cont_byte(y_z, w); } } Some((ch, i)) } match next_code_point(bytes) { Some((ch, count)) => Some((unsafe { crate::str::char_from_u32(ch) }, count)), None => None, } } pub const fn str_count_chars(s: &str) -> usize { let mut s = s.as_bytes(); let mut ans = 0; while let Some((_, count)) = next_char(s) { s = advance(s, count); ans += 1; } ans } pub const fn str_chars(s: &str) -> [char; N] { let mut s = s.as_bytes(); let mut buf: [char; N] = ['\0'; N]; let mut pos = 0; while let Some((ch, count)) = next_char(s) { s = advance(s, count); buf[pos] = ch; pos += 1; } assert!(pos == N); buf } #[cfg(test)] mod tests { use super::*; #[test] fn test_char_encode_utf8() { macro_rules! test_char_encode_utf8 { ($ch: expr) => {{ let e = CharEncodeUtf8::new($ch); let output = e.as_str(); let mut ans = [0; 4]; let ans = $ch.encode_utf8(&mut ans); assert_eq!(output, ans); }}; } test_char_encode_utf8!('\0'); test_char_encode_utf8!('我'); test_char_encode_utf8!('\u{10ffff}'); } #[test] fn test_char_escape_unicode() { macro_rules! test_char_escape_unicode { ($ch: expr) => {{ let e = CharEscapeUnicode::new($ch); let output = e.as_str(); let ans = $ch.escape_unicode().to_string(); assert_eq!(output, ans); }}; } test_char_escape_unicode!('\0'); test_char_escape_unicode!('我'); test_char_escape_unicode!('\u{10ffff}'); } #[test] fn test_char_escape_debug() { macro_rules! test_char_escape_debug { ($ch: expr) => {{ let e = CharEscapeDebug::new($ch, CharEscapeDebugArgs::ESCAPE_ALL); let output = e.as_str(); let ans = $ch.escape_debug().to_string(); assert_eq!(output, ans); }}; } for ch in '\0'..='\u{7f}' { test_char_escape_debug!(ch); } // test_char_escape_debug!('我'); test_char_escape_debug!('\u{10ffff}'); } #[test] fn test_str_chars() { const X: &str = "唐可可"; const OUTPUT_LEN: usize = str_count_chars(X); const OUTPUT_BUF: [char; OUTPUT_LEN] = str_chars::(X); let ans = X.chars().collect::>(); assert_eq!(OUTPUT_BUF, ans.as_slice()); } }