├── .gitignore ├── raw ├── Cargo.toml └── src │ ├── error.rs │ ├── helpers.rs │ └── lib.rs ├── macros ├── tests │ └── macros.rs ├── Cargo.toml ├── README.md ├── LICENSE-MIT ├── src │ └── lib.rs └── LICENSE-APACHE ├── .travis.yml ├── examples └── main.rs ├── LICENSE-MIT ├── .github └── workflows │ └── build-test.yml ├── benches ├── bench_iai.rs ├── binarysearch.rs ├── match.rs ├── construct.rs └── tinystr.rs ├── CHANGELOG.md ├── tests ├── serde.rs └── main.rs ├── Cargo.toml ├── src ├── tinystrauto.rs ├── lib.rs ├── macros.rs ├── ule.rs ├── tinystr4.rs ├── tinystr8.rs └── tinystr16.rs ├── README.md └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /raw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinystr-raw" 3 | description = """ 4 | Raw string-to-integer conversions for tinystr. 5 | """ 6 | version = "0.1.3" 7 | authors = ["Zibi Braniecki ", "Shane F. Carr "] 8 | edition = "2018" 9 | license = "Apache-2.0/MIT" 10 | repository = "https://github.com/zbraniecki/tinystr" 11 | keywords = ["string", "str", "small", "tiny", "no_std"] 12 | categories = ["data-structures"] 13 | 14 | [features] 15 | std = [] -------------------------------------------------------------------------------- /macros/tests/macros.rs: -------------------------------------------------------------------------------- 1 | use tinystr_macros::*; 2 | 3 | #[test] 4 | fn test_u32() { 5 | const VALUE: u32 = u32_from_bytes!("aabb"); 6 | assert_eq!(0x62626161, VALUE); 7 | } 8 | 9 | #[test] 10 | fn test_u64() { 11 | const VALUE: u64 = u64_from_bytes!("aaaabbbb"); 12 | assert_eq!(0x6262626261616161, VALUE); 13 | } 14 | 15 | #[test] 16 | fn test_u128() { 17 | const VALUE: u128 = u128_from_bytes!("aaaaaaaabbbbbbbb"); 18 | assert_eq!(0x62626262626262626161616161616161, VALUE); 19 | } 20 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinystr-macros" 3 | description = """ 4 | Proc macros for TinyStr. 5 | """ 6 | version = "0.2.0" 7 | authors = ["Zibi Braniecki ", "Shane F. Carr "] 8 | edition = "2018" 9 | license = "Apache-2.0/MIT" 10 | repository = "https://github.com/zbraniecki/tinystr" 11 | readme = "README.md" 12 | keywords = ["string", "str", "small", "tiny", "no_std"] 13 | categories = ["data-structures"] 14 | 15 | [lib] 16 | proc_macro = true 17 | 18 | [dependencies] 19 | tinystr-raw = { version = "0.1", path = "../raw" } 20 | -------------------------------------------------------------------------------- /macros/README.md: -------------------------------------------------------------------------------- 1 | A set of proc macros for [`TinyStr`](https://crates.io/crates/tinystr) allowing for: 2 | 3 | ```rust 4 | use tinystr::macros::tinystr4; 5 | 6 | let UND: TinyStr4 = tinystr4!("und"); 7 | assert_eq!(UND, "und"); 8 | ``` 9 | 10 | Status 11 | ------ 12 | 13 | The crate is fully functional and ready to be used in production. 14 | The capabilities can be extended. 15 | 16 | #### License 17 | 18 | 19 | Licensed under either of Apache License, Version 20 | 2.0 or MIT license at your option. 21 | 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: required 3 | cache: cargo 4 | dist: trusty 5 | addons: 6 | apt: 7 | packages: 8 | - libssl-dev 9 | rust: 10 | - nightly 11 | - beta 12 | - stable 13 | 14 | before_cache: | 15 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 16 | cargo install cargo-tarpaulin -f 17 | fi 18 | 19 | script: 20 | - cargo clean 21 | - cargo build --features serde 22 | - cargo test --features serde 23 | 24 | 25 | after_success: | 26 | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then 27 | cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID 28 | fi 29 | -------------------------------------------------------------------------------- /examples/main.rs: -------------------------------------------------------------------------------- 1 | use tinystr::{TinyStr4, TinyStr8}; 2 | 3 | fn main() { 4 | let s1: TinyStr4 = "tEsT".parse().expect("Failed to parse."); 5 | 6 | assert_eq!(s1, "tEsT"); 7 | assert_eq!(s1.to_ascii_uppercase(), "TEST"); 8 | assert_eq!(s1.to_ascii_lowercase(), "test"); 9 | assert_eq!(s1.to_ascii_titlecase(), "Test"); 10 | assert_eq!(s1.is_ascii_alphanumeric(), true); 11 | 12 | let s2: TinyStr8 = "New York".parse().expect("Failed to parse."); 13 | 14 | assert_eq!(s2, "New York"); 15 | assert_eq!(s2.to_ascii_uppercase(), "NEW YORK"); 16 | assert_eq!(s2.to_ascii_lowercase(), "new york"); 17 | assert_eq!(s2.is_ascii_alphanumeric(), false); 18 | } 19 | -------------------------------------------------------------------------------- /raw/src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | #[cfg(feature = "std")] 4 | use std::error; 5 | 6 | /// Enum to store the various types of errors that can cause parsing a TinyStr to fail. 7 | #[derive(PartialEq, Eq, Debug)] 8 | pub enum Error { 9 | /// String is too large or too small to store as TinyStr. 10 | InvalidSize, 11 | /// String is empty. 12 | InvalidNull, 13 | /// String contains non-ASCII character(s). 14 | NonAscii, 15 | } 16 | 17 | impl fmt::Display for Error { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | match self { 20 | Error::InvalidSize => write!(f, "invalid size"), 21 | Error::InvalidNull => write!(f, "string is empty"), 22 | Error::NonAscii => write!(f, "contains non-ASCII"), 23 | } 24 | } 25 | } 26 | 27 | #[cfg(feature = "std")] 28 | impl error::Error for Error {} 29 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | on: [push] 3 | jobs: 4 | test: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | target: 9 | # Little-endian: 10 | - x86_64-unknown-linux-gnu 11 | # Big-endian: 12 | - powerpc-unknown-linux-gnu 13 | directory: 14 | - . 15 | - raw 16 | - macros 17 | runs-on: ubuntu-latest 18 | defaults: 19 | run: 20 | working-directory: ${{ matrix.directory }} 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions-rs/cargo@v1.0.1 24 | name: Check (No Features) 25 | with: 26 | command: check 27 | use-cross: true 28 | args: --all-targets --no-default-features --target ${{ matrix.target }} 29 | - uses: actions-rs/cargo@v1.0.1 30 | name: Check (All Features) 31 | with: 32 | command: check 33 | use-cross: true 34 | args: --all-targets --all-features --target ${{ matrix.target }} 35 | - uses: actions-rs/cargo@v1.0.1 36 | name: Test 37 | with: 38 | command: test 39 | use-cross: true 40 | args: --all-features --target ${{ matrix.target }} 41 | -------------------------------------------------------------------------------- /benches/bench_iai.rs: -------------------------------------------------------------------------------- 1 | use criterion::black_box; 2 | use tinystr::*; 3 | 4 | const HAYSTACK: &[TinyStr4] = &[ 5 | tinystr4!("ar"), 6 | tinystr4!("be"), 7 | tinystr4!("de"), 8 | tinystr4!("en"), 9 | tinystr4!("fr"), 10 | tinystr4!("it"), 11 | tinystr4!("pl"), 12 | tinystr4!("ru"), 13 | tinystr4!("sk"), 14 | tinystr4!("zh"), 15 | ]; 16 | 17 | const NEEDLE: TinyStr4 = tinystr4!("en"); 18 | 19 | fn iai_tinystr() { 20 | if let Ok(idx) = black_box(HAYSTACK).binary_search(&NEEDLE) { 21 | let _ = black_box(idx); 22 | } 23 | } 24 | 25 | const HAYSTACK_U32: &[u32] = &[ 26 | tinystr4!("ar").as_unsigned(), 27 | tinystr4!("be").as_unsigned(), 28 | tinystr4!("de").as_unsigned(), 29 | tinystr4!("en").as_unsigned(), 30 | tinystr4!("fr").as_unsigned(), 31 | tinystr4!("it").as_unsigned(), 32 | tinystr4!("pl").as_unsigned(), 33 | tinystr4!("ru").as_unsigned(), 34 | tinystr4!("sk").as_unsigned(), 35 | tinystr4!("zh").as_unsigned(), 36 | ]; 37 | 38 | const NEEDLE_U32: u32 = tinystr4!("en").as_unsigned(); 39 | 40 | fn iai_tinystr_as_unsigned() { 41 | if let Ok(idx) = black_box(HAYSTACK_U32).binary_search(&NEEDLE_U32) { 42 | let _ = black_box(idx); 43 | } 44 | } 45 | 46 | iai::main!(iai_tinystr, iai_tinystr_as_unsigned); 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | - … 6 | 7 | ## tinystr 0.4.5 (April 14, 2021) 8 | 9 | - Fix `Ord` implementation to follow `str` sorting. 10 | 11 | 12 | ## tinystr 0.4.4 (April 14, 2021) (yanked) 13 | 14 | - Inline more methods to reduce instruction count. In particular, speed up `binary_search` 15 | 16 | ## tinystr 0.4.3 (February 18, 2021) 17 | 18 | - Implement `Error` for errors. 19 | - Implement `serde` support. 20 | 21 | ## tinystr 0.4.2 (January 14, 2021) 22 | 23 | - Add `repr(transparent)`. 24 | 25 | ## tinystr 0.4.1 (October 13, 2020) 26 | 27 | - Improve macro re-export. 28 | - cont-ify more functions. 29 | 30 | ## tinystr 0.4.0 (October 12, 2020) 31 | 32 | - Improve macros ergonomics by separating `tinystr-raw` crate. 33 | 34 | ## tinystr 0.3.4 (August 21, 2020) 35 | 36 | - Add `macros` feature which exposes `tinystr::macros`. 37 | 38 | ## tinystr 0.3.3 (July 26, 2020) 39 | 40 | - Add `TinyStrAuto`. 41 | - Add `no_std` feature. 42 | 43 | ## tinystr 0.3.2 (October 28, 2019) 44 | 45 | - Add `from_bytes` method. 46 | 47 | ## tinystr 0.3.1 (October 1, 2019) 48 | 49 | - Documentation. 50 | 51 | ## tinystr 0.3.1 (October 1, 2019) 52 | 53 | - Documentation. 54 | 55 | ## tinystr 0.3.0 (August 23, 2019) 56 | 57 | - Separate out `is_ascii_numeric`, `is_ascii_alphanumeric` and `is_ascii_alphabetic`. 58 | 59 | ## tinystr 0.2.0 (August 16, 2019) 60 | 61 | - Add TinyStr16 62 | - Add to_ascii_titlecase specialization for all TinyStr* 63 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `tinystr-macros` exports proc macros to convert byte strings to raw TinyStr data. 2 | //! 3 | //! Not intended for public consumption; use `tinystr` instead. 4 | 5 | extern crate proc_macro; 6 | 7 | // use proc_macro::bridge::client::Literal as BridgeLiteral; 8 | use proc_macro::{Literal, TokenStream, TokenTree}; 9 | 10 | fn get_value_from_token_stream(input: TokenStream) -> String { 11 | let val = input.to_string(); 12 | if !val.starts_with('"') && !val.ends_with('"') { 13 | panic!("Expected a string literal; found {:?}", input); 14 | } 15 | (&val[1..val.len() - 1]).to_string() 16 | } 17 | 18 | #[proc_macro] 19 | pub fn u32_from_bytes(input: TokenStream) -> TokenStream { 20 | let s = get_value_from_token_stream(input); 21 | let u = tinystr_raw::try_u32_from_bytes(s.as_bytes()).expect(&s); 22 | TokenTree::from(Literal::u32_suffixed(u.into())).into() 23 | } 24 | 25 | #[proc_macro] 26 | pub fn u64_from_bytes(input: TokenStream) -> TokenStream { 27 | let s = get_value_from_token_stream(input); 28 | let u = tinystr_raw::try_u64_from_bytes(s.as_bytes()) 29 | .expect("Failed to construct TinyStr from input"); 30 | TokenTree::from(Literal::u64_suffixed(u.into())).into() 31 | } 32 | 33 | #[proc_macro] 34 | pub fn u128_from_bytes(input: TokenStream) -> TokenStream { 35 | let s = get_value_from_token_stream(input); 36 | let u = tinystr_raw::try_u128_from_bytes(s.as_bytes()) 37 | .expect("Failed to construct TinyStr from input"); 38 | TokenTree::from(Literal::u128_suffixed(u.into())).into() 39 | } 40 | -------------------------------------------------------------------------------- /tests/serde.rs: -------------------------------------------------------------------------------- 1 | use serde_json; 2 | use tinystr::*; 3 | 4 | macro_rules! test_roundtrip { 5 | ($f:ident, $ty:ident, $val:expr, $bincode:expr) => { 6 | #[test] 7 | fn $f() { 8 | let tiny: $ty = $val.parse().unwrap(); 9 | let json_string = serde_json::to_string(&tiny).unwrap(); 10 | let expected_json = concat!("\"", $val, "\""); 11 | assert_eq!(json_string, expected_json); 12 | let recover: $ty = serde_json::from_str(&json_string).unwrap(); 13 | assert_eq!(&*tiny, &*recover); 14 | 15 | let bin = bincode::serialize(&tiny).unwrap(); 16 | assert_eq!(bin, $bincode); 17 | let debin: $ty = bincode::deserialize(&bin).unwrap(); 18 | assert_eq!(&*tiny, &*debin); 19 | } 20 | }; 21 | } 22 | 23 | test_roundtrip!(test_roundtrip4_1, TinyStr4, "en", [101, 110, 0, 0]); 24 | test_roundtrip!(test_roundtrip4_2, TinyStr4, "Latn", [76, 97, 116, 110]); 25 | test_roundtrip!( 26 | test_roundtrip8, 27 | TinyStr8, 28 | "calendar", 29 | [99, 97, 108, 101, 110, 100, 97, 114] 30 | ); 31 | test_roundtrip!( 32 | test_roundtrip16, 33 | TinyStr16, 34 | "verylongstring", 35 | [118, 101, 114, 121, 108, 111, 110, 103, 115, 116, 114, 105, 110, 103, 0, 0] 36 | ); 37 | test_roundtrip!( 38 | test_roundtripauto_1, 39 | TinyStrAuto, 40 | "shortstring", 41 | [11, 0, 0, 0, 0, 0, 0, 0, 115, 104, 111, 114, 116, 115, 116, 114, 105, 110, 103] 42 | ); 43 | test_roundtrip!( 44 | test_roundtripauto_2, 45 | TinyStrAuto, 46 | "veryveryverylongstring", 47 | [ 48 | 22, 0, 0, 0, 0, 0, 0, 0, 118, 101, 114, 121, 118, 101, 114, 121, 118, 101, 114, 121, 108, 49 | 111, 110, 103, 115, 116, 114, 105, 110, 103 50 | ] 51 | ); 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinystr" 3 | description = """ 4 | A small ASCII-only bounded length string representation. 5 | """ 6 | version = "0.4.12" 7 | authors = [ 8 | "Raph Levien ", 9 | "Zibi Braniecki ", 10 | "Shane F. Carr ", 11 | "Manish Goregaokar " 12 | ] 13 | edition = "2018" 14 | license = "Apache-2.0/MIT" 15 | repository = "https://github.com/zbraniecki/tinystr" 16 | readme = "README.md" 17 | keywords = ["string", "str", "small", "tiny", "no_std"] 18 | categories = ["data-structures"] 19 | # needed to avoid dev-dependency features unifying 20 | resolver = "2" 21 | 22 | [dependencies] 23 | serde = { version = "1.0.123", optional = true, default-features = false, features = ["alloc"] } 24 | tinystr-macros = { version = "0.2", path = "./macros" } 25 | tinystr-raw = { version = "0.1.3", path = "./raw" } 26 | zerovec = {version = "0.5.0", optional = true } 27 | 28 | [dev-dependencies] 29 | criterion = "0.3" 30 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 31 | bincode = "1.3" 32 | iai = "0.1" 33 | rand = "0.8" 34 | rand_pcg = "0.3" 35 | rand_distr = "0.4" 36 | 37 | [features] 38 | default = [ "std" ] # Default to using the std 39 | 40 | # Use the standard library. Enables TinyStrAuto. 41 | std = ["tinystr-raw/std"] 42 | 43 | # Use the `alloc` crate. Enables TinyStrAuto. This feature does nothing if std is enabled. 44 | alloc = [] 45 | 46 | [package.metadata.docs.rs] 47 | all-features = true 48 | 49 | [[bench]] 50 | name = "construct" 51 | harness = false 52 | required-features = ["std"] 53 | 54 | [[bench]] 55 | name = "tinystr" 56 | harness = false 57 | 58 | [[bench]] 59 | name = "match" 60 | harness = false 61 | 62 | [[bench]] 63 | name = "binarysearch" 64 | harness = false 65 | required-features = ["std"] 66 | 67 | [[bench]] 68 | name = "bench_iai" 69 | harness = false 70 | required-features = ["std"] 71 | 72 | [[test]] 73 | name = "serde" 74 | required-features = ["serde"] 75 | -------------------------------------------------------------------------------- /benches/binarysearch.rs: -------------------------------------------------------------------------------- 1 | use criterion::black_box; 2 | use criterion::criterion_group; 3 | use criterion::criterion_main; 4 | use criterion::Criterion; 5 | 6 | use tinystr::{TinyStr16, TinyStr4, TinyStr8}; 7 | 8 | static STRINGS_4: &[&str] = &[ 9 | "en", "es", "it", "zh", "de", "arab", "pl", "fr", "sr", "nb", "mk", "uk", "hans", "und", "ug", 10 | "mn", "lif", "gan", "yue", "unr", "tuq", "klx", "kk", "cyrl", 11 | ]; 12 | 13 | macro_rules! bench_block { 14 | ($r:ty, $group:expr, $name:expr) => { 15 | let keys: Vec<$r> = STRINGS_4.iter().map(|s| s.parse::<$r>().unwrap()).collect(); 16 | 17 | // Create about 36000 entries, with 2, 3 and 4 characters. 18 | // Some keys will not be present in this data. 19 | let mut strings = Vec::new(); 20 | for i in 'a'..='z' { 21 | for j in 'a'..='z' { 22 | let raw = [i as u8, j as u8]; 23 | strings.push(<$r>::from_bytes(&raw).unwrap()); 24 | for k in 'a'..='z' { 25 | let raw = [i as u8, j as u8, k as u8]; 26 | strings.push(<$r>::from_bytes(&raw).unwrap()); 27 | let raw = [i as u8, j as u8, i as u8, k as u8]; 28 | strings.push(<$r>::from_bytes(&raw).unwrap()); 29 | } 30 | } 31 | } 32 | strings.sort_unstable(); 33 | 34 | $group.bench_function($name, |b| { 35 | b.iter(|| { 36 | for key in keys.iter() { 37 | let _ = black_box(strings.binary_search_by_key(&key, |l| l)); 38 | } 39 | }) 40 | }); 41 | }; 42 | } 43 | 44 | fn binarysearch_bench(c: &mut Criterion) { 45 | let mut group = c.benchmark_group("binarysearch"); 46 | bench_block!(TinyStr4, group, "tinystr4"); 47 | bench_block!(TinyStr8, group, "tinystr8"); 48 | bench_block!(TinyStr16, group, "tinystr16"); 49 | group.finish(); 50 | } 51 | 52 | criterion_group!(benches, binarysearch_bench); 53 | criterion_main!(benches); 54 | -------------------------------------------------------------------------------- /raw/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use core::num::{NonZeroU128, NonZeroU32, NonZeroU64}; 2 | use core::ptr::copy_nonoverlapping; 3 | 4 | use super::Error; 5 | 6 | #[inline(always)] 7 | pub(crate) unsafe fn make_u32_bytes( 8 | bytes: &[u8], 9 | len: usize, 10 | mask: u32, 11 | ) -> Result { 12 | // Mask is always supplied as little-endian. 13 | let mask = u32::from_le(mask); 14 | let mut word: u32 = 0; 15 | copy_nonoverlapping(bytes.as_ptr(), &mut word as *mut u32 as *mut u8, len); 16 | if (word & mask) != 0 { 17 | return Err(Error::NonAscii); 18 | } 19 | if ((mask - word) & mask) != 0 { 20 | return Err(Error::InvalidNull); 21 | } 22 | Ok(NonZeroU32::new_unchecked(word)) 23 | } 24 | 25 | #[inline(always)] 26 | pub(crate) unsafe fn make_u64_bytes( 27 | bytes: &[u8], 28 | len: usize, 29 | mask: u64, 30 | ) -> Result { 31 | // TODO: could do this with #cfg(target_endian), but this is clearer and 32 | // more confidence-inspiring. 33 | let mask = u64::from_le(mask); 34 | let mut word: u64 = 0; 35 | copy_nonoverlapping(bytes.as_ptr(), &mut word as *mut u64 as *mut u8, len); 36 | if (word & mask) != 0 { 37 | return Err(Error::NonAscii); 38 | } 39 | if ((mask - word) & mask) != 0 { 40 | return Err(Error::InvalidNull); 41 | } 42 | Ok(NonZeroU64::new_unchecked(word)) 43 | } 44 | 45 | #[inline(always)] 46 | pub(crate) unsafe fn make_u128_bytes( 47 | bytes: &[u8], 48 | len: usize, 49 | mask: u128, 50 | ) -> Result { 51 | // TODO: could do this with #cfg(target_endian), but this is clearer and 52 | // more confidence-inspiring. 53 | let mask = u128::from_le(mask); 54 | let mut word: u128 = 0; 55 | copy_nonoverlapping(bytes.as_ptr(), &mut word as *mut u128 as *mut u8, len); 56 | if (word & mask) != 0 { 57 | return Err(Error::NonAscii); 58 | } 59 | if ((mask - word) & mask) != 0 { 60 | return Err(Error::InvalidNull); 61 | } 62 | Ok(NonZeroU128::new_unchecked(word)) 63 | } 64 | -------------------------------------------------------------------------------- /raw/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `tinystr-raw` exports functions to convert byte strings to raw TinyStr data. 2 | //! 3 | //! Not intended for public consumption; use `tinystr` instead. 4 | 5 | #![cfg_attr(not(feature = "std"), no_std)] 6 | 7 | mod error; 8 | mod helpers; 9 | 10 | pub use error::Error; 11 | 12 | use core::num::{NonZeroU128, NonZeroU32, NonZeroU64}; 13 | 14 | #[inline(always)] 15 | pub fn try_u32_from_bytes(bytes: &[u8]) -> Result { 16 | unsafe { 17 | match bytes.len() { 18 | 1 => helpers::make_u32_bytes(bytes, 1, 0x80), 19 | 2 => helpers::make_u32_bytes(bytes, 2, 0x8080), 20 | 3 => helpers::make_u32_bytes(bytes, 3, 0x0080_8080), 21 | 4 => helpers::make_u32_bytes(bytes, 4, 0x8080_8080), 22 | _ => Err(Error::InvalidSize), 23 | } 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_u32_from_bytes() { 29 | assert_eq!( 30 | NonZeroU32::new(if cfg!(target_endian = "little") { 31 | 0x6262_6161 32 | } else { 33 | 0x6161_6262 34 | }) 35 | .unwrap(), 36 | try_u32_from_bytes(b"aabb").unwrap() 37 | ); 38 | } 39 | 40 | #[inline(always)] 41 | pub fn try_u64_from_bytes(bytes: &[u8]) -> Result { 42 | let len = bytes.len(); 43 | if !(1..=8).contains(&len) { 44 | return Err(Error::InvalidSize); 45 | } 46 | let mask = 0x8080_8080_8080_8080_u64 >> (8 * (8 - len)); 47 | unsafe { helpers::make_u64_bytes(bytes, len, mask) } 48 | } 49 | 50 | #[test] 51 | fn test_u64_from_bytes() { 52 | assert_eq!( 53 | NonZeroU64::new(if cfg!(target_endian = "little") { 54 | 0x6262_6262_6161_6161 55 | } else { 56 | 0x6161_6161_6262_6262 57 | }) 58 | .unwrap(), 59 | try_u64_from_bytes(b"aaaabbbb").unwrap() 60 | ); 61 | } 62 | 63 | #[inline(always)] 64 | pub fn try_u128_from_bytes(bytes: &[u8]) -> Result { 65 | let len = bytes.len(); 66 | if !(1..=16).contains(&len) { 67 | return Err(Error::InvalidSize); 68 | } 69 | let mask = 0x8080_8080_8080_8080_8080_8080_8080_8080_u128 >> (8 * (16 - len)); 70 | unsafe { helpers::make_u128_bytes(bytes, len, mask) } 71 | } 72 | 73 | #[test] 74 | fn test_u128_from_bytes() { 75 | assert_eq!( 76 | NonZeroU128::new(if cfg!(target_endian = "little") { 77 | 0x6262_6262_6262_6262_6161_6161_6161_6161 78 | } else { 79 | 0x6161_6161_6161_6161_6262_6262_6262_6262 80 | }) 81 | .unwrap(), 82 | try_u128_from_bytes(b"aaaaaaaabbbbbbbb").unwrap() 83 | ); 84 | } 85 | -------------------------------------------------------------------------------- /src/tinystrauto.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::Deref; 3 | use std::str::FromStr; 4 | 5 | use crate::Error; 6 | use crate::TinyStr16; 7 | 8 | #[cfg(any(feature = "std", test))] 9 | pub use std::string::String; 10 | 11 | #[cfg(all(not(feature = "std"), not(test)))] 12 | extern crate alloc; 13 | 14 | #[cfg(all(not(feature = "std"), not(test)))] 15 | pub use alloc::string::String; 16 | 17 | /// An ASCII string that is tiny when <= 16 chars and a String otherwise. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// use tinystr::TinyStrAuto; 23 | /// 24 | /// let s1: TinyStrAuto = "Testing".parse() 25 | /// .expect("Failed to parse."); 26 | /// 27 | /// assert_eq!(s1, "Testing"); 28 | /// ``` 29 | #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] 30 | pub enum TinyStrAuto { 31 | /// Up to 16 characters stored on the stack. 32 | Tiny(TinyStr16), 33 | /// 17 or more characters stored on the heap. 34 | Heap(String), 35 | } 36 | 37 | impl fmt::Display for TinyStrAuto { 38 | #[inline(always)] 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | self.deref().fmt(f) 41 | } 42 | } 43 | 44 | impl Deref for TinyStrAuto { 45 | type Target = str; 46 | 47 | #[inline(always)] 48 | fn deref(&self) -> &str { 49 | use TinyStrAuto::*; 50 | match self { 51 | Tiny(value) => value.deref(), 52 | Heap(value) => value.deref(), 53 | } 54 | } 55 | } 56 | 57 | impl PartialEq<&str> for TinyStrAuto { 58 | #[inline(always)] 59 | fn eq(&self, other: &&str) -> bool { 60 | self.deref() == *other 61 | } 62 | } 63 | 64 | impl FromStr for TinyStrAuto { 65 | type Err = Error; 66 | 67 | fn from_str(text: &str) -> Result { 68 | if text.len() <= 16 { 69 | TinyStr16::from_str(text).map(TinyStrAuto::Tiny) 70 | } else if text.is_ascii() { 71 | Ok(TinyStrAuto::Heap(text.into())) 72 | } else { 73 | Err(Error::NonAscii) 74 | } 75 | } 76 | } 77 | 78 | #[cfg(feature = "serde")] 79 | impl serde::Serialize for TinyStrAuto { 80 | fn serialize(&self, serializer: S) -> Result 81 | where 82 | S: serde::Serializer, 83 | { 84 | serializer.serialize_str(&self) 85 | } 86 | } 87 | 88 | #[cfg(feature = "serde")] 89 | impl<'de> serde::Deserialize<'de> for TinyStrAuto { 90 | fn deserialize(deserializer: D) -> Result 91 | where 92 | D: serde::Deserializer<'de>, 93 | { 94 | use serde::de::Error as SerdeError; 95 | use alloc::borrow::Cow; 96 | #[cfg(not(feature = "std"))] 97 | use alloc::string::ToString; 98 | 99 | let x: Cow<'de, str> = serde::Deserialize::deserialize(deserializer)?; 100 | x.parse() 101 | .map_err(|e: Error| SerdeError::custom(e.to_string())) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinystr [![crates.io](https://img.shields.io/crates/v/tinystr.svg)](https://crates.io/crates/tinystr) [![Build Status](https://travis-ci.org/zbraniecki/tinystr.svg?branch=master)](https://travis-ci.org/zbraniecki/tinystr) [![Coverage Status](https://coveralls.io/repos/github/zbraniecki/tinystr/badge.svg?branch=master)](https://coveralls.io/github/zbraniecki/tinystr?branch=master) 2 | 3 | `tinystr` is a small ASCII-only bounded length string representation. 4 | 5 | Usage 6 | ----- 7 | 8 | ```rust 9 | use tinystr::{TinyStr4, TinyStr8, TinyStr16, TinyStrAuto}; 10 | 11 | fn main() { 12 | let s1: TinyStr4 = "tEsT".parse() 13 | .expect("Failed to parse."); 14 | 15 | assert_eq!(s1, "tEsT"); 16 | assert_eq!(s1.to_ascii_uppercase(), "TEST"); 17 | assert_eq!(s1.to_ascii_lowercase(), "test"); 18 | assert_eq!(s1.to_ascii_titlecase(), "Test"); 19 | assert_eq!(s1.is_ascii_alphanumeric(), true); 20 | 21 | let s2: TinyStr8 = "New York".parse() 22 | .expect("Failed to parse."); 23 | 24 | assert_eq!(s2, "New York"); 25 | assert_eq!(s2.to_ascii_uppercase(), "NEW YORK"); 26 | assert_eq!(s2.to_ascii_lowercase(), "new york"); 27 | assert_eq!(s2.to_ascii_titlecase(), "New york"); 28 | assert_eq!(s2.is_ascii_alphanumeric(), false); 29 | 30 | let s3: TinyStr16 = "metaMoRphosis123".parse() 31 | .expect("Failed to parse."); 32 | 33 | assert_eq!(s3, "metaMoRphosis123"); 34 | assert_eq!(s3.to_ascii_uppercase(), "METAMORPHOSIS123"); 35 | assert_eq!(s3.to_ascii_lowercase(), "metamorphosis123"); 36 | assert_eq!(s3.to_ascii_titlecase(), "Metamorphosis123"); 37 | assert_eq!(s3.is_ascii_alphanumeric(), true); 38 | 39 | let s4: TinyStrAuto = "shortNoAlloc".parse().unwrap(); 40 | assert!(matches!(s4, TinyStrAuto::Tiny { .. })); 41 | assert_eq!(s4, "shortNoAlloc"); 42 | 43 | let s5: TinyStrAuto = "longFallbackToHeap".parse().unwrap(); 44 | assert!(matches!(s4, TinyStrAuto::Heap { .. })); 45 | assert_eq!(s4, "shortNoAlloc"); 46 | } 47 | ``` 48 | 49 | Details 50 | ------- 51 | 52 | The crate provides three structs and an enum: 53 | * `TinyStr4` an ASCII-only string limited to 4 characters. 54 | * `TinyStr8` an ASCII-only string limited to 8 characters. 55 | * `TinyStr16` an ASCII-only string limited to 16 characters. 56 | * `TinyStrAuto` (enum): 57 | * `Tiny` when the string is 16 characters or less. 58 | * `Heap` when the string is 17 or more characters. 59 | 60 | The structs stores the characters as `u32`/`u64`/`u128` and uses bitmasking to provide basic string manipulation operations: 61 | * is_ascii_numeric 62 | * is_ascii_alphabetic 63 | * is_ascii_alphanumeric 64 | * to_ascii_lowercase 65 | * to_ascii_uppercase 66 | * to_ascii_titlecase 67 | * PartialEq 68 | 69 | `TinyStrAuto` stores the string as a TinyStr16 when it is short enough, or else falls back to a standard `String`. You should use TinyStrAuto when you expect most strings to be 16 characters or smaller, but occasionally you receive one that exceeds that length. Unlike the structs, `TinyStrAuto` does not implement `Copy`. 70 | 71 | This set is sufficient for certain classes of uses such as `unic-langid` libraries. 72 | 73 | no_std 74 | ------ 75 | 76 | Disable the `std` feature of this crate to make it `#[no_std]`. Doing so disables `TinyStrAuto`. You 77 | can re-enable `TinyStrAuto` in `#[no_std]` mode by enabling the `alloc` feature. 78 | 79 | Performance 80 | ----------- 81 | 82 | For those uses, TinyStr provides [performance characteristics](https://github.com/zbraniecki/tinystr/wiki/Performance) much better than the regular `String`. 83 | 84 | Status 85 | ------ 86 | 87 | The crate is fully functional and ready to be used in production. 88 | The capabilities can be extended. 89 | 90 | #### License 91 | 92 | 93 | Licensed under either of Apache License, Version 94 | 2.0 or MIT license at your option. 95 | { 18 | unsafe { $crate::TinyStr4::new_unchecked($crate::raw_macros::u32_from_bytes!($s)) } 19 | }; 20 | } 21 | 22 | #[test] 23 | fn test_tinystr4() { 24 | use crate::TinyStr4; 25 | const X1: TinyStr4 = tinystr4!("foo"); 26 | let x2: TinyStr4 = "foo".parse().unwrap(); 27 | assert_eq!(X1, x2); 28 | } 29 | 30 | /// Macro to create a const TinyStr8, validated with zero runtime cost. 31 | /// 32 | /// The argument must be a string literal: 33 | /// https://doc.rust-lang.org/reference/tokens.html#string-literals 34 | /// 35 | /// # Example 36 | /// 37 | /// ``` 38 | /// use tinystr::{tinystr8, TinyStr8}; 39 | /// 40 | /// const S1: TinyStr8 = tinystr8!("abcdefg"); 41 | /// let s2: TinyStr8 = "abcdefg".parse().unwrap(); 42 | /// assert_eq!(S1, s2); 43 | /// ``` 44 | #[macro_export] 45 | macro_rules! tinystr8 { 46 | ($s:literal) => { 47 | unsafe { $crate::TinyStr8::new_unchecked($crate::raw_macros::u64_from_bytes!($s)) } 48 | }; 49 | } 50 | 51 | #[test] 52 | fn test_tinystr8() { 53 | use crate::TinyStr8; 54 | const X1: TinyStr8 = tinystr8!("barbaz"); 55 | let x2: TinyStr8 = "barbaz".parse().unwrap(); 56 | assert_eq!(X1, x2); 57 | } 58 | 59 | /// Macro to create a const TinyStr8, validated with zero runtime cost. 60 | /// 61 | /// The argument must be a string literal: 62 | /// https://doc.rust-lang.org/reference/tokens.html#string-literals 63 | /// 64 | /// # Example 65 | /// 66 | /// ``` 67 | /// use tinystr::{tinystr16, TinyStr16}; 68 | /// 69 | /// const S1: TinyStr16 = tinystr16!("longer-string"); 70 | /// let s2: TinyStr16 = "longer-string".parse().unwrap(); 71 | /// assert_eq!(S1, s2); 72 | /// ``` 73 | #[macro_export] 74 | macro_rules! tinystr16 { 75 | ($s:literal) => { 76 | unsafe { $crate::TinyStr16::new_unchecked($crate::raw_macros::u128_from_bytes!($s)) } 77 | }; 78 | } 79 | 80 | // Internal macro for implementing Serialize/Deserialize 81 | macro_rules! serde_impl { 82 | ($ty:ident, $int:ident) => { 83 | #[cfg(feature = "serde")] 84 | impl serde::Serialize for $ty { 85 | fn serialize(&self, serializer: S) -> Result 86 | where 87 | S: serde::Serializer, 88 | { 89 | if serializer.is_human_readable() { 90 | serializer.serialize_str(self.as_str()) 91 | } else { 92 | self.as_unsigned().to_le().serialize(serializer) 93 | } 94 | } 95 | } 96 | 97 | #[cfg(feature = "serde")] 98 | impl<'de> serde::Deserialize<'de> for $ty { 99 | fn deserialize(deserializer: D) -> Result<$ty, D::Error> 100 | where 101 | D: serde::Deserializer<'de>, 102 | { 103 | use serde::de::Error as SerdeError; 104 | use alloc::borrow::Cow; 105 | use alloc::string::ToString; 106 | 107 | if deserializer.is_human_readable() { 108 | let x: Cow<'de, str> = serde::Deserialize::deserialize(deserializer)?; 109 | x.parse() 110 | .map_err(|e: Error| SerdeError::custom(e.to_string())) 111 | } else { 112 | // little-endian 113 | let le = serde::Deserialize::deserialize(deserializer)?; 114 | let bytes = $int::from_le(le).to_ne_bytes(); 115 | let bytes = bytes.split(|t| *t == 0).next().ok_or_else(|| { 116 | SerdeError::custom(concat!("Empty string found for ", stringify!($ty))) 117 | })?; 118 | <$ty>::from_bytes(&bytes).map_err(|e| SerdeError::custom(e.to_string())) 119 | } 120 | } 121 | } 122 | }; 123 | } 124 | #[test] 125 | fn test_tinystr16() { 126 | use crate::TinyStr16; 127 | const X1: TinyStr16 = tinystr16!("metamorphosis"); 128 | let x2: TinyStr16 = "metamorphosis".parse().unwrap(); 129 | assert_eq!(X1, x2); 130 | } 131 | -------------------------------------------------------------------------------- /src/ule.rs: -------------------------------------------------------------------------------- 1 | // This file is part of ICU4X. For terms of use, please see the file 2 | // called LICENSE at the top level of the ICU4X source tree 3 | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). 4 | 5 | //! This module contains adapters to allow `tinystr` to work with [`zerovec`](https://docs.rs/zerovec) 6 | //! and is enabled by enabling the `"zerovec"` feature of the `tinystr` crate. 7 | 8 | use crate::{Error, TinyStr16, TinyStr4, TinyStr8}; 9 | use std::mem; 10 | use zerovec::ule::{AsULE, PlainOldULE, ULE}; 11 | 12 | /// This is an unaligned little-endian version of TinyStr. It MUST contain a nonempty 13 | /// ASCII-only byte sequence. 14 | /// 15 | /// TinyStr is already endian-agnostic (like str), so the only difference is alignment. 16 | /// 17 | /// This type is made available by enabling the `"zerovec"` feature of the `tinystr` crate. 18 | #[repr(transparent)] 19 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 20 | pub struct AsciiULE(PlainOldULE); 21 | 22 | macro_rules! impl_str_ule_size { 23 | ($size:literal, $tiny:ty, $integer:ty) => { 24 | impl From<$tiny> for AsciiULE<$size> { 25 | fn from(s: $tiny) -> Self { 26 | // This converts between endiannesses twice: TinyStr::into converts 27 | // from little-endian into native, and PlainOldULE::from 28 | // converts from native to little again 29 | let int: $integer = s.into(); 30 | AsciiULE(int.into()) 31 | } 32 | } 33 | 34 | /// This impl is made available by enabling the `"zerovec"` feature of the `tinystr` crate. 35 | impl AsULE for $tiny { 36 | type ULE = AsciiULE<$size>; 37 | #[inline] 38 | fn as_unaligned(self) -> Self::ULE { 39 | self.into() 40 | } 41 | #[inline] 42 | fn from_unaligned(unaligned: Self::ULE) -> Self { 43 | unsafe { 44 | // This is safe since AsciiULE guarantees that it comes from 45 | // a valid TinyStr 46 | 47 | // This converts between endiannesses twice: TinyStr::new_unchecked() 48 | // takes in a native endian integer, which we produce via from_unaligned() 49 | Self::new_unchecked(<$integer>::from_unaligned(unaligned.0)) 50 | } 51 | } 52 | } 53 | 54 | impl AsciiULE<$size> { 55 | #[inline] 56 | pub fn as_bytes(&self) -> &[u8] { 57 | self.0.as_bytes() 58 | } 59 | } 60 | 61 | /// This impl is made available by enabling the `"zerovec"` feature of the `tinystr` crate. 62 | unsafe impl ULE for AsciiULE<$size> { 63 | type Error = Error; 64 | #[inline] 65 | fn validate_byte_slice(bytes: &[u8]) -> Result<(), Self::Error> { 66 | debug_assert!(mem::size_of::<$tiny>() == mem::size_of::<[u8; $size]>()); 67 | 68 | let data = bytes.as_ptr(); 69 | let len = bytes.len() / $size; 70 | 71 | let bytes_slice: &[[u8; $size]] = 72 | unsafe { std::slice::from_raw_parts(data as *const [u8; $size], len) }; 73 | for bytes in bytes_slice { 74 | let bytes = bytes.split(|t| *t == 0).next().ok_or(Error::InvalidNull)?; 75 | let _ = <$tiny>::from_bytes(&*bytes)?; 76 | } 77 | Ok(()) 78 | } 79 | } 80 | }; 81 | } 82 | 83 | impl_str_ule_size!(4, TinyStr4, u32); 84 | impl_str_ule_size!(8, TinyStr8, u64); 85 | impl_str_ule_size!(16, TinyStr16, u128); 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | use std::vec; 91 | use std::vec::Vec; 92 | 93 | #[test] 94 | fn test_roundtrip() { 95 | let strings = vec!["en", "us", "zh-CN"]; 96 | let tinies: Vec = strings.iter().map(|s| s.parse().unwrap()).collect(); 97 | let individually_converted: Vec> = 98 | tinies.iter().map(|s| s.as_unaligned()).collect(); 99 | let slice = AsciiULE::as_byte_slice(&individually_converted); 100 | let parsed_ules = AsciiULE::<8>::parse_byte_slice(slice).expect("Slice must parse"); 101 | assert_eq!(individually_converted, parsed_ules); 102 | let recouped_tinies: Vec = parsed_ules 103 | .iter() 104 | .copied() 105 | .map(TinyStr8::from_unaligned) 106 | .collect(); 107 | assert_eq!(tinies, recouped_tinies); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /benches/match.rs: -------------------------------------------------------------------------------- 1 | use criterion::black_box; 2 | use criterion::criterion_group; 3 | use criterion::criterion_main; 4 | use criterion::Criterion; 5 | 6 | use tinystr::{tinystr16, tinystr4, tinystr8}; 7 | use tinystr::{TinyStr16, TinyStr4, TinyStr8}; 8 | 9 | macro_rules! count { 10 | () => (0usize); 11 | ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); 12 | } 13 | 14 | macro_rules! gen_match { 15 | ($TinyType:ident, [$( ($item:ident, $tiny:expr) ),*]) => { 16 | const STRINGS: [&str; count!($($item)*)] = [ 17 | $(stringify!($item),)* 18 | ]; 19 | const TINYSTRS: [$TinyType; count!($($item)*)] = [ 20 | $($tiny,)* 21 | ]; 22 | fn match_str(s: &str) -> &'static str { 23 | match s { 24 | $(stringify!($item) => stringify!($item),)* 25 | _ => "", 26 | } 27 | } 28 | #[allow(non_upper_case_globals)] 29 | fn match_tiny(t: $TinyType) -> &'static str { 30 | $(const $item: $TinyType = $tiny;)* 31 | match t { 32 | $($item => stringify!($item),)* 33 | _ => "", 34 | } 35 | } 36 | } 37 | } 38 | 39 | fn tinystr4_match(c: &mut Criterion) { 40 | gen_match!( 41 | TinyStr4, 42 | [ 43 | (US, tinystr4!("US")), 44 | (GB, tinystr4!("GB")), 45 | (AR, tinystr4!("AR")), 46 | (Hans, tinystr4!("Hans")), 47 | (CN, tinystr4!("CN")), 48 | (AT, tinystr4!("AT")), 49 | (PL, tinystr4!("PL")), 50 | (Cyrl, tinystr4!("Cyrl")), 51 | (SR, tinystr4!("SR")), 52 | (NO, tinystr4!("NO")), 53 | (FR, tinystr4!("FR")), 54 | (MK, tinystr4!("MK")), 55 | (UK, tinystr4!("UK")), 56 | (ZH, tinystr4!("ZH")), 57 | (Mymr, tinystr4!("Mymr")) 58 | ] 59 | ); 60 | 61 | c.bench_function("match str 4", |b| { 62 | b.iter(|| { 63 | for s in STRINGS.iter() { 64 | black_box(match_str(s)); 65 | } 66 | }) 67 | }); 68 | 69 | c.bench_function("match tiny 4", |b| { 70 | b.iter(|| { 71 | for s in TINYSTRS.iter() { 72 | black_box(match_tiny(*s)); 73 | } 74 | }) 75 | }); 76 | } 77 | 78 | fn tinystr8_match(c: &mut Criterion) { 79 | gen_match!( 80 | TinyStr8, 81 | [ 82 | (Latn, tinystr8!("Latn")), 83 | (windows, tinystr8!("windows")), 84 | (AR, tinystr8!("AR")), 85 | (Hans, tinystr8!("Hans")), 86 | (macos, tinystr8!("macos")), 87 | (AT, tinystr8!("AT")), 88 | (pl, tinystr8!("pl")), 89 | (FR, tinystr8!("FR")), 90 | (en, tinystr8!("en")), 91 | (Cyrl, tinystr8!("Cyrl")), 92 | (SR, tinystr8!("SR")), 93 | (NO, tinystr8!("NO")), 94 | (A419, tinystr8!("A419")), 95 | (und, tinystr8!("und")), 96 | (UK, tinystr8!("UK")) 97 | ] 98 | ); 99 | 100 | c.bench_function("match str 8", |b| { 101 | b.iter(|| { 102 | for s in STRINGS.iter() { 103 | black_box(match_str(s)); 104 | } 105 | }) 106 | }); 107 | 108 | c.bench_function("match tiny 8", |b| { 109 | b.iter(|| { 110 | for s in TINYSTRS.iter() { 111 | black_box(match_tiny(*s)); 112 | } 113 | }) 114 | }); 115 | } 116 | 117 | fn tinystr16_match(c: &mut Criterion) { 118 | gen_match!( 119 | TinyStr16, 120 | [ 121 | (Latn, tinystr16!("Latn")), 122 | (windows, tinystr16!("windows")), 123 | (AR, tinystr16!("AR")), 124 | (Hans, tinystr16!("Hans")), 125 | (macos, tinystr16!("macos")), 126 | (AT, tinystr16!("AT")), 127 | (infiniband, tinystr16!("infiniband")), 128 | (FR, tinystr16!("FR")), 129 | (en, tinystr16!("en")), 130 | (Cyrl, tinystr16!("Cyrl")), 131 | (FromIntegral, tinystr16!("FromIntegral")), 132 | (NO, tinystr16!("NO")), 133 | (A419, tinystr16!("A419")), 134 | (MacintoshOSX2019, tinystr16!("MacintoshOSX2019")), 135 | (UK, tinystr16!("UK")) 136 | ] 137 | ); 138 | 139 | c.bench_function("match str 16", |b| { 140 | b.iter(|| { 141 | for s in STRINGS.iter() { 142 | black_box(match_str(s)); 143 | } 144 | }) 145 | }); 146 | 147 | c.bench_function("match tiny 16", |b| { 148 | b.iter(|| { 149 | for s in TINYSTRS.iter() { 150 | black_box(match_tiny(*s)); 151 | } 152 | }) 153 | }); 154 | } 155 | 156 | criterion_group!(benches, tinystr4_match, tinystr8_match, tinystr16_match,); 157 | criterion_main!(benches); 158 | -------------------------------------------------------------------------------- /benches/construct.rs: -------------------------------------------------------------------------------- 1 | use criterion::black_box; 2 | use criterion::criterion_group; 3 | use criterion::criterion_main; 4 | use criterion::Bencher; 5 | use criterion::Criterion; 6 | 7 | use tinystr::{TinyStr16, TinyStr4, TinyStr8, TinyStrAuto}; 8 | 9 | static STRINGS_4: &[&str] = &[ 10 | "US", "GB", "AR", "Hans", "CN", "AT", "PL", "FR", "AT", "Cyrl", "SR", "NO", "FR", "MK", "UK", 11 | ]; 12 | 13 | static STRINGS_8: &[&str] = &[ 14 | "Latn", "windows", "AR", "Hans", "macos", "AT", "pl", "FR", "en", "Cyrl", "SR", "NO", "419", 15 | "und", "UK", 16 | ]; 17 | 18 | static STRINGS_16: &[&str] = &[ 19 | "Latn", 20 | "windows", 21 | "AR", 22 | "Hans", 23 | "macos", 24 | "AT", 25 | "infiniband", 26 | "FR", 27 | "en", 28 | "Cyrl", 29 | "FromIntegral", 30 | "NO", 31 | "419", 32 | "MacintoshOSX2019", 33 | "UK", 34 | ]; 35 | 36 | macro_rules! bench_block { 37 | ($c:expr, $name:expr, $action:ident) => { 38 | let mut group4 = $c.benchmark_group(&format!("{}/4", $name)); 39 | group4.bench_function("String", $action!(String, STRINGS_4)); 40 | group4.bench_function("TinyStr4", $action!(TinyStr4, STRINGS_4)); 41 | group4.bench_function("TinyStr8", $action!(TinyStr8, STRINGS_4)); 42 | group4.bench_function("TinyStr16", $action!(TinyStr16, STRINGS_4)); 43 | group4.bench_function("TinyStrAuto", $action!(TinyStrAuto, STRINGS_4)); 44 | group4.finish(); 45 | 46 | let mut group8 = $c.benchmark_group(&format!("{}/8", $name)); 47 | group8.bench_function("String", $action!(String, STRINGS_8)); 48 | group8.bench_function("TinyStr8", $action!(TinyStr8, STRINGS_8)); 49 | group8.bench_function("TinyStr16", $action!(TinyStr16, STRINGS_8)); 50 | group8.bench_function("TinyStrAuto", $action!(TinyStrAuto, STRINGS_8)); 51 | group8.finish(); 52 | 53 | let mut group16 = $c.benchmark_group(&format!("{}/16", $name)); 54 | group16.bench_function("String", $action!(String, STRINGS_16)); 55 | group16.bench_function("TinyStr16", $action!(TinyStr16, STRINGS_16)); 56 | group16.bench_function("TinyStrAuto", $action!(TinyStrAuto, STRINGS_16)); 57 | group16.finish(); 58 | }; 59 | } 60 | 61 | fn construct_from_str(c: &mut Criterion) { 62 | macro_rules! cfs { 63 | ($r:ty, $inputs:expr) => { 64 | |b: &mut Bencher| { 65 | b.iter(|| { 66 | for s in $inputs { 67 | let _: $r = black_box(s.parse().unwrap()); 68 | } 69 | }) 70 | } 71 | }; 72 | } 73 | 74 | bench_block!(c, "construct_from_str", cfs); 75 | } 76 | 77 | fn construct_from_bytes(c: &mut Criterion) { 78 | macro_rules! cfu { 79 | ($r:ty, $inputs:expr) => { 80 | |b| { 81 | let raw: Vec<&[u8]> = $inputs.iter().map(|s| s.as_bytes()).collect(); 82 | b.iter(move || { 83 | for u in &raw { 84 | let _ = black_box(<$r>::from_bytes(*u).unwrap()); 85 | } 86 | }) 87 | } 88 | }; 89 | } 90 | 91 | let mut group4 = c.benchmark_group("construct_from_bytes/4"); 92 | group4.bench_function("TinyStr4", cfu!(TinyStr4, STRINGS_4)); 93 | group4.bench_function("TinyStr8", cfu!(TinyStr8, STRINGS_4)); 94 | group4.bench_function("TinyStr16", cfu!(TinyStr16, STRINGS_4)); 95 | group4.finish(); 96 | 97 | let mut group8 = c.benchmark_group("construct_from_bytes/8"); 98 | group8.bench_function("TinyStr8", cfu!(TinyStr8, STRINGS_8)); 99 | group8.bench_function("TinyStr16", cfu!(TinyStr16, STRINGS_8)); 100 | group8.finish(); 101 | 102 | let mut group16 = c.benchmark_group("construct_from_bytes/16"); 103 | group16.bench_function("TinyStr16", cfu!(TinyStr16, STRINGS_16)); 104 | group16.finish(); 105 | } 106 | 107 | fn construct_unchecked(c: &mut Criterion) { 108 | macro_rules! cu { 109 | ($tty:ty, $rty:ty, $inputs:expr) => { 110 | |b| { 111 | let raw: Vec<$rty> = $inputs 112 | .iter() 113 | .map(|s| s.parse::<$tty>().unwrap().into()) 114 | .collect(); 115 | b.iter(move || { 116 | for num in &raw { 117 | let _ = unsafe { <$tty>::new_unchecked(black_box(*num)) }; 118 | } 119 | }) 120 | } 121 | }; 122 | } 123 | 124 | let mut group4 = c.benchmark_group("construct_unchecked/4"); 125 | group4.bench_function("TinyStr4", cu!(TinyStr4, u32, STRINGS_4)); 126 | group4.finish(); 127 | 128 | let mut group8 = c.benchmark_group("construct_unchecked/8"); 129 | group8.bench_function("TinyStr8", cu!(TinyStr8, u64, STRINGS_8)); 130 | group8.finish(); 131 | 132 | let mut group16 = c.benchmark_group("construct_unchecked/16"); 133 | group16.bench_function("TinyStr16", cu!(TinyStr16, u128, STRINGS_16)); 134 | group16.finish(); 135 | } 136 | 137 | criterion_group!( 138 | benches, 139 | construct_from_str, 140 | construct_from_bytes, 141 | construct_unchecked, 142 | ); 143 | criterion_main!(benches); 144 | -------------------------------------------------------------------------------- /benches/tinystr.rs: -------------------------------------------------------------------------------- 1 | use criterion::black_box; 2 | use criterion::criterion_group; 3 | use criterion::criterion_main; 4 | use criterion::Bencher; 5 | use criterion::Criterion; 6 | 7 | use tinystr::{TinyStr16, TinyStr4, TinyStr8}; 8 | 9 | static STRINGS_4: &[&str] = &[ 10 | "US", "GB", "AR", "Hans", "CN", "AT", "PL", "FR", "AT", "Cyrl", "SR", "NO", "FR", "MK", "UK", 11 | ]; 12 | 13 | static STRINGS_8: &[&str] = &[ 14 | "Latn", "windows", "AR", "Hans", "macos", "AT", "pl", "FR", "en", "Cyrl", "SR", "NO", "419", 15 | "und", "UK", 16 | ]; 17 | 18 | static STRINGS_16: &[&str] = &[ 19 | "Latn", 20 | "windows", 21 | "AR", 22 | "Hans", 23 | "macos", 24 | "AT", 25 | "infiniband", 26 | "FR", 27 | "en", 28 | "Cyrl", 29 | "FromIntegral", 30 | "NO", 31 | "419", 32 | "MacintoshOSX2019", 33 | "UK", 34 | ]; 35 | 36 | macro_rules! bench_block { 37 | ($c:expr, $name:expr, $action:ident) => { 38 | let mut group4 = $c.benchmark_group(&format!("{}/4", $name)); 39 | group4.bench_function("String", $action!(String, STRINGS_4)); 40 | group4.bench_function("TinyStr4", $action!(TinyStr4, STRINGS_4)); 41 | group4.bench_function("TinyStr8", $action!(TinyStr8, STRINGS_4)); 42 | group4.bench_function("TinyStr16", $action!(TinyStr16, STRINGS_4)); 43 | group4.finish(); 44 | 45 | let mut group8 = $c.benchmark_group(&format!("{}/8", $name)); 46 | group8.bench_function("String", $action!(String, STRINGS_8)); 47 | group8.bench_function("TinyStr8", $action!(TinyStr8, STRINGS_8)); 48 | group8.bench_function("TinyStr16", $action!(TinyStr16, STRINGS_8)); 49 | group8.finish(); 50 | 51 | let mut group16 = $c.benchmark_group(&format!("{}/16", $name)); 52 | group16.bench_function("String", $action!(String, STRINGS_16)); 53 | group16.bench_function("TinyStr16", $action!(TinyStr16, STRINGS_16)); 54 | group16.finish(); 55 | }; 56 | } 57 | 58 | macro_rules! convert_to_ascii { 59 | ($ty:ty, $action:ident, $inputs:expr) => { 60 | |b: &mut Bencher| { 61 | let raw: Vec<$ty> = $inputs.iter().map(|s| s.parse::<$ty>().unwrap()).collect(); 62 | b.iter(move || { 63 | for s in &raw { 64 | let _ = black_box(s.$action()); 65 | } 66 | }) 67 | } 68 | }; 69 | } 70 | 71 | fn convert_to_ascii_lowercase(c: &mut Criterion) { 72 | macro_rules! ctal { 73 | ($ty:ty, $inputs:expr) => { 74 | convert_to_ascii!($ty, to_ascii_lowercase, $inputs) 75 | }; 76 | } 77 | 78 | bench_block!(c, "convert_to_ascii_lowercase", ctal); 79 | } 80 | 81 | fn convert_to_ascii_uppercase(c: &mut Criterion) { 82 | macro_rules! ctau { 83 | ($ty:ty, $inputs:expr) => { 84 | convert_to_ascii!($ty, to_ascii_uppercase, $inputs) 85 | }; 86 | } 87 | 88 | bench_block!(c, "convert_to_ascii_uppercase", ctau); 89 | } 90 | 91 | trait ExtToAsciiTitlecase { 92 | fn to_ascii_titlecase(&self) -> String; 93 | } 94 | 95 | impl ExtToAsciiTitlecase for str { 96 | fn to_ascii_titlecase(&self) -> String { 97 | let mut result = self.to_ascii_lowercase(); 98 | result[0..1].make_ascii_uppercase(); 99 | result 100 | } 101 | } 102 | 103 | fn convert_to_ascii_titlecase(c: &mut Criterion) { 104 | macro_rules! ctat { 105 | ($ty:ty, $inputs:expr) => { 106 | convert_to_ascii!($ty, to_ascii_titlecase, $inputs) 107 | }; 108 | } 109 | 110 | bench_block!(c, "convert_to_ascii_titlecase", ctat); 111 | } 112 | 113 | trait ExtIsAsciiAlphanumeric { 114 | fn is_ascii_alphanumeric(&self) -> bool; 115 | } 116 | 117 | impl ExtIsAsciiAlphanumeric for str { 118 | fn is_ascii_alphanumeric(&self) -> bool { 119 | self.chars().all(|c| c.is_ascii_alphanumeric()) 120 | } 121 | } 122 | 123 | fn test_is_ascii_alphanumeric(c: &mut Criterion) { 124 | macro_rules! tiaa { 125 | ($ty:ty, $inputs:expr) => { 126 | |b: &mut Bencher| { 127 | let raw: Vec<$ty> = $inputs.iter().map(|s| s.parse::<$ty>().unwrap()).collect(); 128 | b.iter(move || { 129 | for s in &raw { 130 | let _ = black_box(s.is_ascii_alphanumeric()); 131 | } 132 | }) 133 | } 134 | }; 135 | } 136 | 137 | bench_block!(c, "test_is_ascii_alphanumeric", tiaa); 138 | } 139 | 140 | fn test_eq(c: &mut Criterion) { 141 | macro_rules! te { 142 | ($ty:ty, $inputs:expr) => { 143 | |b: &mut Bencher| { 144 | let raw: Vec<$ty> = $inputs.iter().map(|s| s.parse::<$ty>().unwrap()).collect(); 145 | b.iter(move || { 146 | for s in &raw { 147 | for l in &raw { 148 | let _ = black_box(s == l); 149 | } 150 | } 151 | }) 152 | } 153 | }; 154 | } 155 | 156 | bench_block!(c, "test_eq", te); 157 | } 158 | 159 | criterion_group!( 160 | benches, 161 | convert_to_ascii_lowercase, 162 | convert_to_ascii_uppercase, 163 | convert_to_ascii_titlecase, 164 | test_is_ascii_alphanumeric, 165 | test_eq, 166 | ); 167 | criterion_main!(benches); 168 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /macros/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/tinystr4.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt; 3 | use std::num::NonZeroU32; 4 | use std::ops::Deref; 5 | use std::str::FromStr; 6 | 7 | use crate::Error; 8 | 9 | /// A tiny string that is from 1 to 4 non-NUL ASCII characters. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use tinystr::TinyStr4; 15 | /// 16 | /// let s1: TinyStr4 = "Test".parse() 17 | /// .expect("Failed to parse."); 18 | /// 19 | /// assert_eq!(s1, "Test"); 20 | /// assert!(s1.is_ascii_alphabetic()); 21 | /// ``` 22 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 23 | #[repr(transparent)] 24 | pub struct TinyStr4(NonZeroU32); 25 | 26 | impl TinyStr4 { 27 | /// Creates a TinyStr4 from a byte slice. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use tinystr::TinyStr4; 33 | /// 34 | /// let s1 = TinyStr4::from_bytes("Test".as_bytes()) 35 | /// .expect("Failed to parse."); 36 | /// 37 | /// assert_eq!(s1, "Test"); 38 | /// ``` 39 | #[inline(always)] 40 | pub fn from_bytes(bytes: &[u8]) -> Result { 41 | tinystr_raw::try_u32_from_bytes(bytes).map(Self) 42 | } 43 | 44 | /// An unsafe constructor intended for cases where the consumer 45 | /// guarantees that the input is a little endian integer which 46 | /// is a correct representation of a `TinyStr4` string. 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use tinystr::TinyStr4; 52 | /// 53 | /// let s1: TinyStr4 = "Test".parse() 54 | /// .expect("Failed to parse."); 55 | /// 56 | /// let num: u32 = s1.into(); 57 | /// 58 | /// let s2 = unsafe { TinyStr4::new_unchecked(num) }; 59 | /// 60 | /// assert_eq!(s1, s2); 61 | /// assert_eq!(s2.as_str(), "Test"); 62 | /// ``` 63 | /// 64 | /// # Safety 65 | /// 66 | /// The method does not validate the `u32` to be properly encoded 67 | /// value for `TinyStr4`. 68 | /// The value can be retrieved via `Into for TinyStr4`. 69 | #[inline(always)] 70 | pub const unsafe fn new_unchecked(text: u32) -> Self { 71 | Self(NonZeroU32::new_unchecked(u32::from_le(text))) 72 | } 73 | 74 | /// Extracts a string slice containing the entire `TinyStr4`. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ``` 79 | /// use tinystr::TinyStr4; 80 | /// 81 | /// let s1: TinyStr4 = "Test".parse() 82 | /// .expect("Failed to parse."); 83 | /// 84 | /// assert_eq!(s1.as_str(), "Test"); 85 | /// ``` 86 | #[inline(always)] 87 | pub fn as_str(&self) -> &str { 88 | self.deref() 89 | } 90 | 91 | /// Gets a representation of this TinyStr4 as a primitive, valid for the 92 | /// current machine. This value is not necessarily compatible with 93 | /// [`TinyStr4::new_unchecked()`], use [`TinyStr4::from_native_unchecked()`] 94 | /// instead. 95 | /// 96 | /// # Examples 97 | /// 98 | /// ``` 99 | /// use tinystr::{tinystr4, TinyStr4}; 100 | /// 101 | /// const fn const_equals(a: TinyStr4, b: TinyStr4) -> bool { 102 | /// a.as_unsigned() == b.as_unsigned() 103 | /// } 104 | /// 105 | /// const S1: TinyStr4 = tinystr4!("foo"); 106 | /// const S2: TinyStr4 = tinystr4!("foo"); 107 | /// const S3: TinyStr4 = tinystr4!("bar"); 108 | /// 109 | /// assert!(const_equals(S1, S2)); 110 | /// assert!(!const_equals(S1, S3)); 111 | /// ``` 112 | pub const fn as_unsigned(&self) -> u32 { 113 | self.0.get() 114 | } 115 | 116 | /// An unsafe constructor intended for cases where the consumer 117 | /// guarantees that the input is a native endian integer which 118 | /// is a correct representation of a `TinyStr4` string 119 | /// 120 | /// # Examples 121 | /// 122 | /// ``` 123 | /// use tinystr::TinyStr4; 124 | /// 125 | /// let s1: TinyStr4 = "Test".parse() 126 | /// .expect("Failed to parse."); 127 | /// 128 | /// let num: u32 = s1.as_unsigned(); 129 | /// 130 | /// let s2 = unsafe { TinyStr4::new_unchecked(num) }; 131 | /// 132 | /// assert_eq!(s1, s2); 133 | /// assert_eq!(s2.as_str(), "Test"); 134 | /// ``` 135 | /// 136 | /// # Safety 137 | /// 138 | /// The method does not validate the `u32` to be properly encoded 139 | /// value for `TinyStr4`. 140 | /// The value can be retrieved via [`TinyStr4::as_unsigned()`]. 141 | #[inline(always)] 142 | pub const unsafe fn from_native_unchecked(text: u32) -> Self { 143 | Self(NonZeroU32::new_unchecked(text)) 144 | } 145 | 146 | /// Checks if the value is composed of ASCII alphabetic characters: 147 | /// 148 | /// * U+0041 'A' ..= U+005A 'Z', or 149 | /// * U+0061 'a' ..= U+007A 'z'. 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use tinystr::TinyStr4; 155 | /// 156 | /// let s1: TinyStr4 = "Test".parse() 157 | /// .expect("Failed to parse."); 158 | /// let s2: TinyStr4 = "Te3t".parse() 159 | /// .expect("Failed to parse."); 160 | /// 161 | /// assert!(s1.is_ascii_alphabetic()); 162 | /// assert!(!s2.is_ascii_alphabetic()); 163 | /// ``` 164 | pub const fn is_ascii_alphabetic(self) -> bool { 165 | let word = self.0.get(); 166 | let mask = (word + 0x7f7f_7f7f) & 0x8080_8080; 167 | let lower = word | 0x2020_2020; 168 | let alpha = !(lower + 0x1f1f_1f1f) | (lower + 0x0505_0505); 169 | (alpha & mask) == 0 170 | } 171 | 172 | /// Checks if the value is composed of ASCII alphanumeric characters: 173 | /// 174 | /// * U+0041 'A' ..= U+005A 'Z', or 175 | /// * U+0061 'a' ..= U+007A 'z', or 176 | /// * U+0030 '0' ..= U+0039 '9'. 177 | /// 178 | /// # Examples 179 | /// 180 | /// ``` 181 | /// use tinystr::TinyStr4; 182 | /// 183 | /// let s1: TinyStr4 = "A15b".parse() 184 | /// .expect("Failed to parse."); 185 | /// let s2: TinyStr4 = "[3@w".parse() 186 | /// .expect("Failed to parse."); 187 | /// 188 | /// assert!(s1.is_ascii_alphanumeric()); 189 | /// assert!(!s2.is_ascii_alphanumeric()); 190 | /// ``` 191 | pub const fn is_ascii_alphanumeric(self) -> bool { 192 | let word = self.0.get(); 193 | let mask = (word + 0x7f7f_7f7f) & 0x8080_8080; 194 | let numeric = !(word + 0x5050_5050) | (word + 0x4646_4646); 195 | let lower = word | 0x2020_2020; 196 | let alpha = !(lower + 0x1f1f_1f1f) | (lower + 0x0505_0505); 197 | (alpha & numeric & mask) == 0 198 | } 199 | 200 | /// Checks if the value is composed of ASCII decimal digits: 201 | /// 202 | /// * U+0030 '0' ..= U+0039 '9'. 203 | /// 204 | /// # Examples 205 | /// 206 | /// ``` 207 | /// use tinystr::TinyStr4; 208 | /// 209 | /// let s1: TinyStr4 = "312".parse() 210 | /// .expect("Failed to parse."); 211 | /// let s2: TinyStr4 = "3d".parse() 212 | /// .expect("Failed to parse."); 213 | /// 214 | /// assert!(s1.is_ascii_numeric()); 215 | /// assert!(!s2.is_ascii_numeric()); 216 | /// ``` 217 | pub const fn is_ascii_numeric(self) -> bool { 218 | let word = self.0.get(); 219 | let mask = (word + 0x7f7f_7f7f) & 0x8080_8080; 220 | let numeric = !(word + 0x5050_5050) | (word + 0x4646_4646); 221 | (numeric & mask) == 0 222 | } 223 | 224 | /// Converts this type to its ASCII lower case equivalent in-place. 225 | /// 226 | /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', other characters are unchanged. 227 | /// 228 | /// # Examples 229 | /// 230 | /// ``` 231 | /// use tinystr::TinyStr4; 232 | /// 233 | /// let s1: TinyStr4 = "TeS3".parse() 234 | /// .expect("Failed to parse."); 235 | /// 236 | /// assert_eq!(s1.to_ascii_lowercase(), "tes3"); 237 | /// ``` 238 | pub const fn to_ascii_lowercase(self) -> Self { 239 | let word = self.0.get(); 240 | let result = word | (((word + 0x3f3f_3f3f) & !(word + 0x2525_2525) & 0x8080_8080) >> 2); 241 | unsafe { Self(NonZeroU32::new_unchecked(result)) } 242 | } 243 | 244 | /// Converts this type to its ASCII title case equivalent in-place. 245 | /// 246 | /// First character, if is an ASCII letter 'a' to 'z' is mapped to 'A' to 'Z', 247 | /// other characters are unchanged. 248 | /// 249 | /// # Examples 250 | /// 251 | /// ``` 252 | /// use tinystr::TinyStr4; 253 | /// 254 | /// let s1: TinyStr4 = "test".parse() 255 | /// .expect("Failed to parse."); 256 | /// 257 | /// assert_eq!(s1.to_ascii_titlecase(), "Test"); 258 | /// ``` 259 | pub const fn to_ascii_titlecase(self) -> Self { 260 | let word = self.0.get().to_le(); 261 | let mask = ((word + 0x3f3f_3f1f) & !(word + 0x2525_2505) & 0x8080_8080) >> 2; 262 | let result = (word | mask) & !(0x20 & mask); 263 | unsafe { Self(NonZeroU32::new_unchecked(u32::from_le(result))) } 264 | } 265 | 266 | /// Converts this type to its ASCII upper case equivalent in-place. 267 | /// 268 | /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', other characters are unchanged. 269 | /// 270 | /// # Examples 271 | /// 272 | /// ``` 273 | /// use tinystr::TinyStr4; 274 | /// 275 | /// let s1: TinyStr4 = "Tes3".parse() 276 | /// .expect("Failed to parse."); 277 | /// 278 | /// assert_eq!(s1.to_ascii_uppercase(), "TES3"); 279 | /// ``` 280 | pub const fn to_ascii_uppercase(self) -> Self { 281 | let word = self.0.get(); 282 | let result = word & !(((word + 0x1f1f_1f1f) & !(word + 0x0505_0505) & 0x8080_8080) >> 2); 283 | unsafe { Self(NonZeroU32::new_unchecked(result)) } 284 | } 285 | } 286 | 287 | impl fmt::Display for TinyStr4 { 288 | #[inline(always)] 289 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 290 | self.deref().fmt(f) 291 | } 292 | } 293 | 294 | impl fmt::Debug for TinyStr4 { 295 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 296 | write!(f, "{:?}", self.deref()) 297 | } 298 | } 299 | 300 | impl Deref for TinyStr4 { 301 | type Target = str; 302 | 303 | #[inline(always)] 304 | fn deref(&self) -> &str { 305 | let word = self.0.get(); 306 | #[cfg(target_endian = "little")] 307 | let len = (4 - word.leading_zeros() / 8) as usize; 308 | #[cfg(target_endian = "big")] 309 | let len = (4 - word.trailing_zeros() / 8) as usize; 310 | unsafe { 311 | let slice = core::slice::from_raw_parts(&self.0 as *const _ as *const u8, len); 312 | std::str::from_utf8_unchecked(slice) 313 | } 314 | } 315 | } 316 | 317 | impl PartialEq<&str> for TinyStr4 { 318 | #[inline(always)] 319 | fn eq(&self, other: &&str) -> bool { 320 | self.deref() == *other 321 | } 322 | } 323 | 324 | impl PartialOrd for TinyStr4 { 325 | #[inline(always)] 326 | fn partial_cmp(&self, other: &Self) -> Option { 327 | Some(self.cmp(other)) 328 | } 329 | } 330 | 331 | impl Ord for TinyStr4 { 332 | #[inline(always)] 333 | fn cmp(&self, other: &Self) -> Ordering { 334 | self.0.get().to_ne_bytes().cmp(&other.0.get().to_ne_bytes()) 335 | } 336 | } 337 | 338 | impl FromStr for TinyStr4 { 339 | type Err = Error; 340 | 341 | #[inline(always)] 342 | fn from_str(text: &str) -> Result { 343 | Self::from_bytes(text.as_bytes()) 344 | } 345 | } 346 | 347 | impl From for u32 { 348 | fn from(input: TinyStr4) -> Self { 349 | input.0.get().to_le() 350 | } 351 | } 352 | 353 | serde_impl!(TinyStr4, u32); 354 | -------------------------------------------------------------------------------- /src/tinystr8.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt; 3 | use std::num::NonZeroU64; 4 | use std::ops::Deref; 5 | use std::str::FromStr; 6 | 7 | use crate::Error; 8 | 9 | /// A tiny string that is from 1 to 8 non-NUL ASCII characters. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use tinystr::TinyStr8; 15 | /// 16 | /// let s1: TinyStr8 = "Testing".parse() 17 | /// .expect("Failed to parse."); 18 | /// 19 | /// assert_eq!(s1, "Testing"); 20 | /// assert!(s1.is_ascii_alphabetic()); 21 | /// ``` 22 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 23 | #[repr(transparent)] 24 | pub struct TinyStr8(NonZeroU64); 25 | 26 | impl TinyStr8 { 27 | /// Creates a TinyStr8 from a byte slice. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use tinystr::TinyStr8; 33 | /// 34 | /// let s1 = TinyStr8::from_bytes("Testing".as_bytes()) 35 | /// .expect("Failed to parse."); 36 | /// 37 | /// assert_eq!(s1, "Testing"); 38 | /// ``` 39 | #[inline(always)] 40 | pub fn from_bytes(bytes: &[u8]) -> Result { 41 | tinystr_raw::try_u64_from_bytes(bytes).map(Self) 42 | } 43 | 44 | /// An unsafe constructor intended for cases where the consumer 45 | /// guarantees that the input is a little endian integer which 46 | /// is a correct representation of a `TinyStr8` string. 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use tinystr::TinyStr8; 52 | /// 53 | /// let s1: TinyStr8 = "Testing".parse() 54 | /// .expect("Failed to parse."); 55 | /// 56 | /// let num: u64 = s1.into(); 57 | /// 58 | /// let s2 = unsafe { TinyStr8::new_unchecked(num) }; 59 | /// 60 | /// assert_eq!(s1, s2); 61 | /// assert_eq!(s2.as_str(), "Testing"); 62 | /// ``` 63 | /// 64 | /// # Safety 65 | /// 66 | /// The method does not validate the `u64` to be properly encoded 67 | /// value for `TinyStr8`. 68 | /// The value can be retrieved via `Into for TinyStr8`. 69 | #[inline(always)] 70 | pub const unsafe fn new_unchecked(text: u64) -> Self { 71 | Self(NonZeroU64::new_unchecked(u64::from_le(text))) 72 | } 73 | 74 | /// Extracts a string slice containing the entire `TinyStr8`. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ``` 79 | /// use tinystr::TinyStr8; 80 | /// 81 | /// let s1: TinyStr8 = "Testing".parse() 82 | /// .expect("Failed to parse."); 83 | /// 84 | /// assert_eq!(s1.as_str(), "Testing"); 85 | /// ``` 86 | #[inline(always)] 87 | pub fn as_str(&self) -> &str { 88 | self.deref() 89 | } 90 | 91 | /// Gets a representation of this TinyStr8 as a primitive, valid for the 92 | /// current machine. This value is not necessarily compatible with 93 | /// [`TinyStr8::new_unchecked()`], use [`TinyStr8::from_native_unchecked()`] 94 | /// instead. 95 | /// 96 | /// # Examples 97 | /// 98 | /// ``` 99 | /// use tinystr::{tinystr8, TinyStr8}; 100 | /// 101 | /// const fn const_equals(a: TinyStr8, b: TinyStr8) -> bool { 102 | /// a.as_unsigned() == b.as_unsigned() 103 | /// } 104 | /// 105 | /// const S1: TinyStr8 = tinystr8!("foo"); 106 | /// const S2: TinyStr8 = tinystr8!("foo"); 107 | /// const S3: TinyStr8 = tinystr8!("bar"); 108 | /// 109 | /// assert!(const_equals(S1, S2)); 110 | /// assert!(!const_equals(S1, S3)); 111 | /// ``` 112 | pub const fn as_unsigned(&self) -> u64 { 113 | self.0.get() 114 | } 115 | 116 | /// An unsafe constructor intended for cases where the consumer 117 | /// guarantees that the input is a native endian integer which 118 | /// is a correct representation of a `TinyStr8` string 119 | /// 120 | /// # Examples 121 | /// 122 | /// ``` 123 | /// use tinystr::TinyStr8; 124 | /// 125 | /// let s1: TinyStr8 = "Test".parse() 126 | /// .expect("Failed to parse."); 127 | /// 128 | /// let num: u64 = s1.as_unsigned(); 129 | /// 130 | /// let s2 = unsafe { TinyStr8::new_unchecked(num) }; 131 | /// 132 | /// assert_eq!(s1, s2); 133 | /// assert_eq!(s2.as_str(), "Test"); 134 | /// ``` 135 | /// 136 | /// # Safety 137 | /// 138 | /// The method does not validate the `u32` to be properly encoded 139 | /// value for `TinyStr8`. 140 | /// The value can be retrieved via [`TinyStr8::as_unsigned()`]. 141 | #[inline(always)] 142 | pub const unsafe fn from_native_unchecked(text: u64) -> Self { 143 | Self(NonZeroU64::new_unchecked(text)) 144 | } 145 | 146 | /// Checks if the value is composed of ASCII alphabetic characters: 147 | /// 148 | /// * U+0041 'A' ..= U+005A 'Z', or 149 | /// * U+0061 'a' ..= U+007A 'z'. 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use tinystr::TinyStr8; 155 | /// 156 | /// let s1: TinyStr8 = "Testing".parse() 157 | /// .expect("Failed to parse."); 158 | /// let s2: TinyStr8 = "Te3ting".parse() 159 | /// .expect("Failed to parse."); 160 | /// 161 | /// assert!(s1.is_ascii_alphabetic()); 162 | /// assert!(!s2.is_ascii_alphabetic()); 163 | /// ``` 164 | pub const fn is_ascii_alphabetic(self) -> bool { 165 | let word = self.0.get(); 166 | let mask = (word + 0x7f7f_7f7f_7f7f_7f7f) & 0x8080_8080_8080_8080; 167 | let lower = word | 0x2020_2020_2020_2020; 168 | let alpha = !(lower + 0x1f1f_1f1f_1f1f_1f1f) | (lower + 0x0505_0505_0505_0505); 169 | (alpha & mask) == 0 170 | } 171 | 172 | /// Checks if the value is composed of ASCII alphanumeric characters: 173 | /// 174 | /// * U+0041 'A' ..= U+005A 'Z', or 175 | /// * U+0061 'a' ..= U+007A 'z', or 176 | /// * U+0030 '0' ..= U+0039 '9'. 177 | /// 178 | /// # Examples 179 | /// 180 | /// ``` 181 | /// use tinystr::TinyStr8; 182 | /// 183 | /// let s1: TinyStr8 = "A15bing".parse() 184 | /// .expect("Failed to parse."); 185 | /// let s2: TinyStr8 = "[3@wing".parse() 186 | /// .expect("Failed to parse."); 187 | /// 188 | /// assert!(s1.is_ascii_alphanumeric()); 189 | /// assert!(!s2.is_ascii_alphanumeric()); 190 | /// ``` 191 | pub const fn is_ascii_alphanumeric(self) -> bool { 192 | let word = self.0.get(); 193 | let mask = (word + 0x7f7f_7f7f_7f7f_7f7f) & 0x8080_8080_8080_8080; 194 | let numeric = !(word + 0x5050_5050_5050_5050) | (word + 0x4646_4646_4646_4646); 195 | let lower = word | 0x2020_2020_2020_2020; 196 | let alpha = !(lower + 0x1f1f_1f1f_1f1f_1f1f) | (lower + 0x0505_0505_0505_0505); 197 | (alpha & numeric & mask) == 0 198 | } 199 | 200 | /// Checks if the value is composed of ASCII decimal digits: 201 | /// 202 | /// * U+0030 '0' ..= U+0039 '9'. 203 | /// 204 | /// # Examples 205 | /// 206 | /// ``` 207 | /// use tinystr::TinyStr8; 208 | /// 209 | /// let s1: TinyStr8 = "3121029".parse() 210 | /// .expect("Failed to parse."); 211 | /// let s2: TinyStr8 = "3d212d".parse() 212 | /// .expect("Failed to parse."); 213 | /// 214 | /// assert!(s1.is_ascii_numeric()); 215 | /// assert!(!s2.is_ascii_numeric()); 216 | /// ``` 217 | pub const fn is_ascii_numeric(self) -> bool { 218 | let word = self.0.get(); 219 | let mask = (word + 0x7f7f_7f7f_7f7f_7f7f) & 0x8080_8080_8080_8080; 220 | let numeric = !(word + 0x5050_5050_5050_5050) | (word + 0x4646_4646_4646_4646); 221 | (numeric & mask) == 0 222 | } 223 | 224 | /// Converts this type to its ASCII lower case equivalent in-place. 225 | /// 226 | /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', other characters are unchanged. 227 | /// 228 | /// # Examples 229 | /// 230 | /// ``` 231 | /// use tinystr::TinyStr8; 232 | /// 233 | /// let s1: TinyStr8 = "TeS3ing".parse() 234 | /// .expect("Failed to parse."); 235 | /// 236 | /// assert_eq!(s1.to_ascii_lowercase(), "tes3ing"); 237 | /// ``` 238 | pub const fn to_ascii_lowercase(self) -> Self { 239 | let word = self.0.get(); 240 | let result = word 241 | | (((word + 0x3f3f_3f3f_3f3f_3f3f) 242 | & !(word + 0x2525_2525_2525_2525) 243 | & 0x8080_8080_8080_8080) 244 | >> 2); 245 | unsafe { Self(NonZeroU64::new_unchecked(result)) } 246 | } 247 | 248 | /// Converts this type to its ASCII title case equivalent in-place. 249 | /// 250 | /// First character, if is an ASCII letter 'a' to 'z' is mapped to 'A' to 'Z', 251 | /// other characters are unchanged. 252 | /// 253 | /// # Examples 254 | /// 255 | /// ``` 256 | /// use tinystr::TinyStr8; 257 | /// 258 | /// let s1: TinyStr8 = "testing".parse() 259 | /// .expect("Failed to parse."); 260 | /// 261 | /// assert_eq!(s1.to_ascii_titlecase(), "Testing"); 262 | /// ``` 263 | pub const fn to_ascii_titlecase(self) -> Self { 264 | let word = self.0.get().to_le(); 265 | let mask = ((word + 0x3f3f_3f3f_3f3f_3f1f) 266 | & !(word + 0x2525_2525_2525_2505) 267 | & 0x8080_8080_8080_8080) 268 | >> 2; 269 | let result = (word | mask) & !(0x20 & mask); 270 | unsafe { Self(NonZeroU64::new_unchecked(u64::from_le(result))) } 271 | } 272 | 273 | /// Converts this type to its ASCII upper case equivalent in-place. 274 | /// 275 | /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', other characters are unchanged. 276 | /// 277 | /// # Examples 278 | /// 279 | /// ``` 280 | /// use tinystr::TinyStr8; 281 | /// 282 | /// let s1: TinyStr8 = "Tes3ing".parse() 283 | /// .expect("Failed to parse."); 284 | /// 285 | /// assert_eq!(s1.to_ascii_uppercase(), "TES3ING"); 286 | /// ``` 287 | pub const fn to_ascii_uppercase(self) -> Self { 288 | let word = self.0.get(); 289 | let result = word 290 | & !(((word + 0x1f1f_1f1f_1f1f_1f1f) 291 | & !(word + 0x0505_0505_0505_0505) 292 | & 0x8080_8080_8080_8080) 293 | >> 2); 294 | unsafe { Self(NonZeroU64::new_unchecked(result)) } 295 | } 296 | } 297 | 298 | impl fmt::Display for TinyStr8 { 299 | #[inline(always)] 300 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 301 | self.deref().fmt(f) 302 | } 303 | } 304 | 305 | impl fmt::Debug for TinyStr8 { 306 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 307 | write!(f, "{:?}", self.deref()) 308 | } 309 | } 310 | 311 | impl Deref for TinyStr8 { 312 | type Target = str; 313 | 314 | #[inline(always)] 315 | fn deref(&self) -> &str { 316 | let word = self.0.get(); 317 | #[cfg(target_endian = "little")] 318 | let len = (8 - word.leading_zeros() / 8) as usize; 319 | #[cfg(target_endian = "big")] 320 | let len = (8 - word.trailing_zeros() / 8) as usize; 321 | unsafe { 322 | let slice = core::slice::from_raw_parts(&self.0 as *const _ as *const u8, len); 323 | std::str::from_utf8_unchecked(slice) 324 | } 325 | } 326 | } 327 | 328 | impl PartialEq<&str> for TinyStr8 { 329 | #[inline(always)] 330 | fn eq(&self, other: &&str) -> bool { 331 | self.deref() == *other 332 | } 333 | } 334 | 335 | impl PartialOrd for TinyStr8 { 336 | #[inline(always)] 337 | fn partial_cmp(&self, other: &Self) -> Option { 338 | Some(self.cmp(other)) 339 | } 340 | } 341 | 342 | impl Ord for TinyStr8 { 343 | #[inline(always)] 344 | fn cmp(&self, other: &Self) -> Ordering { 345 | self.0.get().to_ne_bytes().cmp(&other.0.get().to_ne_bytes()) 346 | } 347 | } 348 | 349 | impl FromStr for TinyStr8 { 350 | type Err = Error; 351 | 352 | #[inline(always)] 353 | fn from_str(text: &str) -> Result { 354 | TinyStr8::from_bytes(text.as_bytes()) 355 | } 356 | } 357 | 358 | impl From for u64 { 359 | fn from(input: TinyStr8) -> Self { 360 | input.0.get().to_le() 361 | } 362 | } 363 | 364 | serde_impl!(TinyStr8, u64); 365 | -------------------------------------------------------------------------------- /src/tinystr16.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt; 3 | use std::num::NonZeroU128; 4 | use std::ops::Deref; 5 | use std::str::FromStr; 6 | 7 | use crate::Error; 8 | 9 | /// A tiny string that is from 1 to 16 non-NUL ASCII characters. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use tinystr::TinyStr16; 15 | /// 16 | /// let s1: TinyStr16 = "Metamorphosis".parse() 17 | /// .expect("Failed to parse."); 18 | /// 19 | /// assert_eq!(s1, "Metamorphosis"); 20 | /// assert!(s1.is_ascii_alphabetic()); 21 | /// ``` 22 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 23 | #[repr(transparent)] 24 | pub struct TinyStr16(NonZeroU128); 25 | 26 | impl TinyStr16 { 27 | /// Creates a TinyStr16 from a byte slice. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use tinystr::TinyStr16; 33 | /// 34 | /// let s1 = TinyStr16::from_bytes("Testing".as_bytes()) 35 | /// .expect("Failed to parse."); 36 | /// 37 | /// assert_eq!(s1, "Testing"); 38 | /// ``` 39 | #[inline(always)] 40 | pub fn from_bytes(bytes: &[u8]) -> Result { 41 | tinystr_raw::try_u128_from_bytes(bytes).map(Self) 42 | } 43 | 44 | /// An unsafe constructor intended for cases where the consumer 45 | /// guarantees that the input is a little endian integer which 46 | /// is a correct representation of a `TinyStr16` string. 47 | /// 48 | /// # Examples 49 | /// 50 | /// ``` 51 | /// use tinystr::TinyStr16; 52 | /// 53 | /// let s1: TinyStr16 = "Metamorphosis".parse() 54 | /// .expect("Failed to parse."); 55 | /// 56 | /// let num: u128 = s1.into(); 57 | /// 58 | /// let s2 = unsafe { TinyStr16::new_unchecked(num) }; 59 | /// 60 | /// assert_eq!(s1, s2); 61 | /// assert_eq!(s2.as_str(), "Metamorphosis"); 62 | /// ``` 63 | /// 64 | /// # Safety 65 | /// 66 | /// The method does not validate the `u128` to be properly encoded 67 | /// value for `TinyStr16`. 68 | /// The value can be retrieved via `Into for TinyStr16`. 69 | #[inline(always)] 70 | pub const unsafe fn new_unchecked(text: u128) -> Self { 71 | Self(NonZeroU128::new_unchecked(u128::from_le(text))) 72 | } 73 | 74 | /// Extracts a string slice containing the entire `TinyStr16`. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ``` 79 | /// use tinystr::TinyStr16; 80 | /// 81 | /// let s1: TinyStr16 = "Metamorphosis".parse() 82 | /// .expect("Failed to parse."); 83 | /// 84 | /// assert_eq!(s1.as_str(), "Metamorphosis"); 85 | /// ``` 86 | #[inline(always)] 87 | pub fn as_str(&self) -> &str { 88 | self.deref() 89 | } 90 | 91 | /// Gets a representation of this TinyStr16 as a primitive, valid for the 92 | /// current machine. This value is not necessarily compatible with 93 | /// [`TinyStr16::new_unchecked()`], use [`TinyStr16::from_native_unchecked()`] 94 | /// instead. 95 | /// 96 | /// # Examples 97 | /// 98 | /// ``` 99 | /// use tinystr::{tinystr16, TinyStr16}; 100 | /// 101 | /// const fn const_equals(a: TinyStr16, b: TinyStr16) -> bool { 102 | /// a.as_unsigned() == b.as_unsigned() 103 | /// } 104 | /// 105 | /// const S1: TinyStr16 = tinystr16!("foo"); 106 | /// const S2: TinyStr16 = tinystr16!("foo"); 107 | /// const S3: TinyStr16 = tinystr16!("bar"); 108 | /// 109 | /// assert!(const_equals(S1, S2)); 110 | /// assert!(!const_equals(S1, S3)); 111 | /// ``` 112 | pub const fn as_unsigned(&self) -> u128 { 113 | self.0.get() 114 | } 115 | 116 | /// An unsafe constructor intended for cases where the consumer 117 | /// guarantees that the input is a native endian integer which 118 | /// is a correct representation of a `TinyStr16` string 119 | /// 120 | /// # Examples 121 | /// 122 | /// ``` 123 | /// use tinystr::TinyStr16; 124 | /// 125 | /// let s1: TinyStr16 = "Test".parse() 126 | /// .expect("Failed to parse."); 127 | /// 128 | /// let num: u128 = s1.as_unsigned(); 129 | /// 130 | /// let s2 = unsafe { TinyStr16::new_unchecked(num) }; 131 | /// 132 | /// assert_eq!(s1, s2); 133 | /// assert_eq!(s2.as_str(), "Test"); 134 | /// ``` 135 | /// 136 | /// # Safety 137 | /// 138 | /// The method does not validate the `u32` to be properly encoded 139 | /// value for `TinyStr16`. 140 | /// The value can be retrieved via [`TinyStr16::as_unsigned()`]. 141 | #[inline(always)] 142 | pub const unsafe fn from_native_unchecked(text: u128) -> Self { 143 | Self(NonZeroU128::new_unchecked(text)) 144 | } 145 | 146 | /// Checks if the value is composed of ASCII alphabetic characters: 147 | /// 148 | /// * U+0041 'A' ..= U+005A 'Z', or 149 | /// * U+0061 'a' ..= U+007A 'z'. 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use tinystr::TinyStr16; 155 | /// 156 | /// let s1: TinyStr16 = "Metamorphosis".parse() 157 | /// .expect("Failed to parse."); 158 | /// let s2: TinyStr16 = "Met3mo4pho!is".parse() 159 | /// .expect("Failed to parse."); 160 | /// 161 | /// assert!(s1.is_ascii_alphabetic()); 162 | /// assert!(!s2.is_ascii_alphabetic()); 163 | /// ``` 164 | pub const fn is_ascii_alphabetic(self) -> bool { 165 | let word = self.0.get(); 166 | let mask = (word + 0x7f7f_7f7f_7f7f_7f7f_7f7f_7f7f_7f7f_7f7f) 167 | & 0x8080_8080_8080_8080_8080_8080_8080_8080; 168 | let lower = word | 0x2020_2020_2020_2020_2020_2020_2020_2020; 169 | let alpha = !(lower + 0x1f1f_1f1f_1f1f_1f1f_1f1f_1f1f_1f1f_1f1f) 170 | | (lower + 0x0505_0505_0505_0505_0505_0505_0505_0505); 171 | (alpha & mask) == 0 172 | } 173 | 174 | /// Checks if the value is composed of ASCII alphanumeric characters: 175 | /// 176 | /// * U+0041 'A' ..= U+005A 'Z', or 177 | /// * U+0061 'a' ..= U+007A 'z', or 178 | /// * U+0030 '0' ..= U+0039 '9'. 179 | /// 180 | /// # Examples 181 | /// 182 | /// ``` 183 | /// use tinystr::TinyStr16; 184 | /// 185 | /// let s1: TinyStr16 = "A15bingA1".parse() 186 | /// .expect("Failed to parse."); 187 | /// let s2: TinyStr16 = "[3@w00Fs1".parse() 188 | /// .expect("Failed to parse."); 189 | /// 190 | /// assert!(s1.is_ascii_alphanumeric()); 191 | /// assert!(!s2.is_ascii_alphanumeric()); 192 | /// ``` 193 | pub const fn is_ascii_alphanumeric(self) -> bool { 194 | let word = self.0.get(); 195 | let mask = (word + 0x7f7f_7f7f_7f7f_7f7f_7f7f_7f7f_7f7f_7f7f) 196 | & 0x8080_8080_8080_8080_8080_8080_8080_8080; 197 | let numeric = !(word + 0x5050_5050_5050_5050_5050_5050_5050_5050) 198 | | (word + 0x4646_4646_4646_4646_4646_4646_4646_4646); 199 | let lower = word | 0x2020_2020_2020_2020_2020_2020_2020_2020; 200 | let alpha = !(lower + 0x1f1f_1f1f_1f1f_1f1f_1f1f_1f1f_1f1f_1f1f) 201 | | (lower + 0x0505_0505_0505_0505_0505_0505_0505_0505); 202 | (alpha & numeric & mask) == 0 203 | } 204 | 205 | /// Checks if the value is composed of ASCII decimal digits: 206 | /// 207 | /// * U+0030 '0' ..= U+0039 '9'. 208 | /// 209 | /// # Examples 210 | /// 211 | /// ``` 212 | /// use tinystr::TinyStr16; 213 | /// 214 | /// let s1: TinyStr16 = "31212314141".parse() 215 | /// .expect("Failed to parse."); 216 | /// let s2: TinyStr16 = "3d3d3d3d".parse() 217 | /// .expect("Failed to parse."); 218 | /// 219 | /// assert!(s1.is_ascii_numeric()); 220 | /// assert!(!s2.is_ascii_numeric()); 221 | /// ``` 222 | pub const fn is_ascii_numeric(self) -> bool { 223 | let word = self.0.get(); 224 | let mask = (word + 0x7f7f_7f7f_7f7f_7f7f_7f7f_7f7f_7f7f_7f7f) 225 | & 0x8080_8080_8080_8080_8080_8080_8080_8080; 226 | let numeric = !(word + 0x5050_5050_5050_5050_5050_5050_5050_5050) 227 | | (word + 0x4646_4646_4646_4646_4646_4646_4646_4646); 228 | (numeric & mask) == 0 229 | } 230 | 231 | /// Converts this type to its ASCII lower case equivalent in-place. 232 | /// 233 | /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', other characters are unchanged. 234 | /// 235 | /// # Examples 236 | /// 237 | /// ``` 238 | /// use tinystr::TinyStr16; 239 | /// 240 | /// let s1: TinyStr16 = "MeTAmOrpHo3sis".parse() 241 | /// .expect("Failed to parse."); 242 | /// 243 | /// assert_eq!(s1.to_ascii_lowercase(), "metamorpho3sis"); 244 | /// ``` 245 | pub const fn to_ascii_lowercase(self) -> Self { 246 | let word = self.0.get(); 247 | let result = word 248 | | (((word + 0x3f3f_3f3f_3f3f_3f3f_3f3f_3f3f_3f3f_3f3f) 249 | & !(word + 0x2525_2525_2525_2525_2525_2525_2525_2525) 250 | & 0x8080_8080_8080_8080_8080_8080_8080_8080) 251 | >> 2); 252 | unsafe { Self(NonZeroU128::new_unchecked(result)) } 253 | } 254 | 255 | /// Converts this type to its ASCII title case equivalent in-place. 256 | /// 257 | /// First character, if is an ASCII letter 'a' to 'z' is mapped to 'A' to 'Z', 258 | /// other characters are unchanged. 259 | /// 260 | /// # Examples 261 | /// 262 | /// ``` 263 | /// use tinystr::TinyStr16; 264 | /// 265 | /// let s1: TinyStr16 = "metamorphosis".parse() 266 | /// .expect("Failed to parse."); 267 | /// 268 | /// assert_eq!(s1.to_ascii_titlecase(), "Metamorphosis"); 269 | /// ``` 270 | pub const fn to_ascii_titlecase(self) -> Self { 271 | let word = self.0.get().to_le(); 272 | let mask = ((word + 0x3f3f_3f3f_3f3f_3f3f_3f3f_3f3f_3f3f_3f1f) 273 | & !(word + 0x2525_2525_2525_2525_2525_2525_2525_2505) 274 | & 0x8080_8080_8080_8080_8080_8080_8080_8080) 275 | >> 2; 276 | let result = (word | mask) & !(0x20 & mask); 277 | unsafe { Self(NonZeroU128::new_unchecked(u128::from_le(result))) } 278 | } 279 | 280 | /// Converts this type to its ASCII upper case equivalent in-place. 281 | /// 282 | /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', other characters are unchanged. 283 | /// 284 | /// # Examples 285 | /// 286 | /// ``` 287 | /// use tinystr::TinyStr16; 288 | /// 289 | /// let s1: TinyStr16 = "Met3amorphosis".parse() 290 | /// .expect("Failed to parse."); 291 | /// 292 | /// assert_eq!(s1.to_ascii_uppercase(), "MET3AMORPHOSIS"); 293 | /// ``` 294 | pub const fn to_ascii_uppercase(self) -> Self { 295 | let word = self.0.get(); 296 | let result = word 297 | & !(((word + 0x1f1f_1f1f_1f1f_1f1f_1f1f_1f1f_1f1f_1f1f) 298 | & !(word + 0x0505_0505_0505_0505_0505_0505_0505_0505) 299 | & 0x8080_8080_8080_8080_8080_8080_8080_8080) 300 | >> 2); 301 | unsafe { Self(NonZeroU128::new_unchecked(result)) } 302 | } 303 | } 304 | 305 | impl fmt::Display for TinyStr16 { 306 | #[inline(always)] 307 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 308 | self.deref().fmt(f) 309 | } 310 | } 311 | 312 | impl fmt::Debug for TinyStr16 { 313 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 314 | write!(f, "{:?}", self.deref()) 315 | } 316 | } 317 | 318 | impl Deref for TinyStr16 { 319 | type Target = str; 320 | 321 | #[inline(always)] 322 | fn deref(&self) -> &str { 323 | let word = self.0.get(); 324 | #[cfg(target_endian = "little")] 325 | let len = (16 - word.leading_zeros() / 8) as usize; 326 | #[cfg(target_endian = "big")] 327 | let len = (16 - word.trailing_zeros() / 8) as usize; 328 | unsafe { 329 | let slice = core::slice::from_raw_parts(&self.0 as *const _ as *const u8, len); 330 | std::str::from_utf8_unchecked(slice) 331 | } 332 | } 333 | } 334 | 335 | impl PartialEq<&str> for TinyStr16 { 336 | #[inline(always)] 337 | fn eq(&self, other: &&str) -> bool { 338 | self.deref() == *other 339 | } 340 | } 341 | 342 | impl PartialOrd for TinyStr16 { 343 | #[inline(always)] 344 | fn partial_cmp(&self, other: &Self) -> Option { 345 | Some(self.cmp(other)) 346 | } 347 | } 348 | 349 | impl Ord for TinyStr16 { 350 | #[inline(always)] 351 | fn cmp(&self, other: &Self) -> Ordering { 352 | self.0.get().to_ne_bytes().cmp(&other.0.get().to_ne_bytes()) 353 | } 354 | } 355 | 356 | impl FromStr for TinyStr16 { 357 | type Err = Error; 358 | 359 | #[inline(always)] 360 | fn from_str(text: &str) -> Result { 361 | Self::from_bytes(text.as_bytes()) 362 | } 363 | } 364 | 365 | impl From for u128 { 366 | fn from(input: TinyStr16) -> Self { 367 | input.0.get().to_le() 368 | } 369 | } 370 | 371 | serde_impl!(TinyStr16, u128); 372 | -------------------------------------------------------------------------------- /tests/main.rs: -------------------------------------------------------------------------------- 1 | use rand::SeedableRng; 2 | use rand_distr::{Alphanumeric, Distribution, Uniform}; 3 | use rand_pcg::Lcg64Xsh32; 4 | use std::fmt::Write; 5 | use std::mem::size_of; 6 | use std::ops::Deref; 7 | use std::ops::RangeInclusive; 8 | use tinystr::{tinystr16, tinystr4, tinystr8, Error, TinyStr16, TinyStr4, TinyStr8}; 9 | 10 | #[cfg(any(feature = "std", feature = "alloc"))] 11 | use tinystr::TinyStrAuto; 12 | 13 | /// Generates an array of random alphanumeric strings. 14 | /// 15 | /// - length = range of lengths for the strings (chosen uniformly at random) 16 | /// - count = number of strings to generate 17 | fn random_alphanums(lengths: RangeInclusive, count: usize) -> Vec { 18 | // Lcg64Xsh32 is a small, fast PRNG. 19 | let mut rng1 = Lcg64Xsh32::seed_from_u64(2021); 20 | let mut rng2 = Lcg64Xsh32::seed_from_u64(rand::Rng::gen(&mut rng1)); 21 | let alpha_dist = Alphanumeric; 22 | let len_dist = Uniform::from(lengths); 23 | len_dist 24 | .sample_iter(&mut rng1) 25 | .take(count) 26 | .map(|len| { 27 | (&alpha_dist) 28 | .sample_iter(&mut rng2) 29 | .take(len) 30 | .map(char::from) 31 | .collect::() 32 | }) 33 | .collect() 34 | } 35 | 36 | #[test] 37 | fn tiny_sizes() { 38 | assert_eq!(4, size_of::()); 39 | assert_eq!(8, size_of::()); 40 | assert_eq!(16, size_of::()); 41 | #[cfg(target_pointer_width = "64")] 42 | assert_eq!(24, size_of::()); 43 | // Note: TinyStrAuto is size 32 even when a smaller TinyStr type is used 44 | #[cfg(all(target_pointer_width = "64", any(feature = "std", feature = "alloc")))] 45 | assert_eq!(32, size_of::()); 46 | } 47 | 48 | #[test] 49 | fn tiny4_basic() { 50 | let s: TinyStr4 = "abc".parse().unwrap(); 51 | assert_eq!(s.deref(), "abc"); 52 | } 53 | 54 | #[test] 55 | fn tiny4_from_bytes() { 56 | let s = TinyStr4::from_bytes(b"abc").unwrap(); 57 | assert_eq!(s.deref(), "abc"); 58 | 59 | assert_eq!( 60 | TinyStr4::from_bytes(&[0, 159, 146, 150]), 61 | Err(Error::NonAscii) 62 | ); 63 | assert_eq!(TinyStr4::from_bytes(&[]), Err(Error::InvalidSize)); 64 | assert_eq!(TinyStr4::from_bytes(&[0]), Err(Error::InvalidNull)); 65 | } 66 | 67 | #[test] 68 | fn tiny4_size() { 69 | assert_eq!("".parse::(), Err(Error::InvalidSize)); 70 | assert!("1".parse::().is_ok()); 71 | assert!("12".parse::().is_ok()); 72 | assert!("123".parse::().is_ok()); 73 | assert!("1234".parse::().is_ok()); 74 | assert_eq!("12345".parse::(), Err(Error::InvalidSize)); 75 | assert_eq!("123456789".parse::(), Err(Error::InvalidSize)); 76 | } 77 | 78 | #[test] 79 | fn tiny4_null() { 80 | assert_eq!("a\u{0}b".parse::(), Err(Error::InvalidNull)); 81 | } 82 | 83 | #[test] 84 | fn tiny4_new_unchecked() { 85 | let reference: TinyStr4 = "en".parse().unwrap(); 86 | let uval: u32 = reference.into(); 87 | let s = unsafe { TinyStr4::new_unchecked(uval) }; 88 | assert_eq!(s, reference); 89 | assert_eq!(s, "en"); 90 | } 91 | 92 | #[test] 93 | fn tiny4_nonascii() { 94 | assert_eq!("\u{4000}".parse::(), Err(Error::NonAscii)); 95 | } 96 | 97 | #[test] 98 | fn tiny4_alpha() { 99 | let s: TinyStr4 = "@aZ[".parse().unwrap(); 100 | assert!(!s.is_ascii_alphabetic()); 101 | assert!(!s.is_ascii_alphanumeric()); 102 | assert_eq!(s.to_ascii_uppercase().as_str(), "@AZ["); 103 | assert_eq!(s.to_ascii_lowercase().as_str(), "@az["); 104 | 105 | assert!("abYZ".parse::().unwrap().is_ascii_alphabetic()); 106 | assert!("abYZ".parse::().unwrap().is_ascii_alphanumeric()); 107 | assert!("a123".parse::().unwrap().is_ascii_alphanumeric()); 108 | assert!(!"a123".parse::().unwrap().is_ascii_alphabetic()); 109 | } 110 | 111 | #[test] 112 | fn tiny4_numeric() { 113 | let s: TinyStr4 = "@aZ[".parse().unwrap(); 114 | assert!(!s.is_ascii_numeric()); 115 | 116 | assert!("0123".parse::().unwrap().is_ascii_numeric()); 117 | } 118 | 119 | #[test] 120 | fn tiny4_titlecase() { 121 | assert_eq!( 122 | "abcd" 123 | .parse::() 124 | .unwrap() 125 | .to_ascii_titlecase() 126 | .as_str(), 127 | "Abcd" 128 | ); 129 | assert_eq!( 130 | "ABCD" 131 | .parse::() 132 | .unwrap() 133 | .to_ascii_titlecase() 134 | .as_str(), 135 | "Abcd" 136 | ); 137 | assert_eq!( 138 | "aBCD" 139 | .parse::() 140 | .unwrap() 141 | .to_ascii_titlecase() 142 | .as_str(), 143 | "Abcd" 144 | ); 145 | assert_eq!( 146 | "A123" 147 | .parse::() 148 | .unwrap() 149 | .to_ascii_titlecase() 150 | .as_str(), 151 | "A123" 152 | ); 153 | assert_eq!( 154 | "123a" 155 | .parse::() 156 | .unwrap() 157 | .to_ascii_titlecase() 158 | .as_str(), 159 | "123a" 160 | ); 161 | } 162 | 163 | #[test] 164 | fn tiny4_ord() { 165 | let mut v: Vec = vec![ 166 | tinystr4!("zh"), 167 | tinystr4!("aab"), 168 | tinystr4!("zzy"), 169 | tinystr4!("fr"), 170 | ]; 171 | v.sort(); 172 | assert_eq!(Some("aab"), v.get(0).map(TinyStr4::as_str)); 173 | assert_eq!(Some("fr"), v.get(1).map(TinyStr4::as_str)); 174 | assert_eq!(Some("zh"), v.get(2).map(TinyStr4::as_str)); 175 | assert_eq!(Some("zzy"), v.get(3).map(TinyStr4::as_str)); 176 | } 177 | 178 | /// Test consistency of TinyStr Ord with String 179 | #[test] 180 | fn tinystr4_ord_consistency() { 181 | let mut string_vec = random_alphanums(2..=4, 100); 182 | let mut tinystr_vec: Vec = string_vec.iter().map(|s| s.parse().unwrap()).collect(); 183 | string_vec.sort(); 184 | tinystr_vec.sort(); 185 | assert_eq!( 186 | string_vec, 187 | tinystr_vec 188 | .iter() 189 | .map(|s| s.as_str().to_string()) 190 | .collect::>() 191 | ); 192 | } 193 | 194 | #[test] 195 | fn tiny4_eq() { 196 | let s1: TinyStr4 = "en".parse().unwrap(); 197 | let s2: TinyStr4 = "fr".parse().unwrap(); 198 | let s3: TinyStr4 = "en".parse().unwrap(); 199 | 200 | assert_eq!(s1, s3); 201 | assert_ne!(s1, s2); 202 | } 203 | 204 | #[test] 205 | fn tiny4_display() { 206 | let s: TinyStr4 = "abcd".parse().unwrap(); 207 | let mut result = String::new(); 208 | write!(result, "{}", s).unwrap(); 209 | assert_eq!(result, "abcd"); 210 | assert_eq!(format!("{}", s), "abcd"); 211 | assert_eq!(format!("{:>8}", s), format!("{:>8}", result)); 212 | } 213 | 214 | #[test] 215 | fn tiny4_debug() { 216 | let s: TinyStr4 = "abcd".parse().unwrap(); 217 | assert_eq!(format!("{:#?}", s), "\"abcd\""); 218 | } 219 | 220 | #[test] 221 | fn tiny8_basic() { 222 | let s: TinyStr8 = "abcde".parse().unwrap(); 223 | assert_eq!(s.deref(), "abcde"); 224 | } 225 | 226 | #[test] 227 | fn tiny8_from_bytes() { 228 | let s = TinyStr8::from_bytes(b"abcde").unwrap(); 229 | assert_eq!(s.deref(), "abcde"); 230 | 231 | assert_eq!( 232 | TinyStr8::from_bytes(&[0, 159, 146, 150]), 233 | Err(Error::NonAscii) 234 | ); 235 | assert_eq!(TinyStr8::from_bytes(&[]), Err(Error::InvalidSize)); 236 | assert_eq!(TinyStr8::from_bytes(&[0]), Err(Error::InvalidNull)); 237 | } 238 | 239 | #[test] 240 | fn tiny8_size() { 241 | assert_eq!("".parse::(), Err(Error::InvalidSize)); 242 | assert!("1".parse::().is_ok()); 243 | assert!("12".parse::().is_ok()); 244 | assert!("123".parse::().is_ok()); 245 | assert!("1234".parse::().is_ok()); 246 | assert!("12345".parse::().is_ok()); 247 | assert!("123456".parse::().is_ok()); 248 | assert!("1234567".parse::().is_ok()); 249 | assert!("12345678".parse::().is_ok()); 250 | assert_eq!("123456789".parse::(), Err(Error::InvalidSize)); 251 | } 252 | 253 | #[test] 254 | fn tiny8_null() { 255 | assert_eq!("a\u{0}b".parse::(), Err(Error::InvalidNull)); 256 | } 257 | 258 | #[test] 259 | fn tiny8_new_unchecked() { 260 | let reference: TinyStr8 = "Windows".parse().unwrap(); 261 | let uval: u64 = reference.into(); 262 | let s = unsafe { TinyStr8::new_unchecked(uval) }; 263 | assert_eq!(s, reference); 264 | assert_eq!(s, "Windows"); 265 | } 266 | 267 | #[test] 268 | fn tiny8_nonascii() { 269 | assert_eq!("\u{4000}".parse::(), Err(Error::NonAscii)); 270 | } 271 | 272 | #[test] 273 | fn tiny8_alpha() { 274 | let s: TinyStr8 = "@abcXYZ[".parse().unwrap(); 275 | assert!(!s.is_ascii_alphabetic()); 276 | assert!(!s.is_ascii_alphanumeric()); 277 | assert_eq!(s.to_ascii_uppercase().as_str(), "@ABCXYZ["); 278 | assert_eq!(s.to_ascii_lowercase().as_str(), "@abcxyz["); 279 | 280 | assert!("abcXYZ".parse::().unwrap().is_ascii_alphabetic()); 281 | assert!("abcXYZ" 282 | .parse::() 283 | .unwrap() 284 | .is_ascii_alphanumeric()); 285 | assert!(!"abc123".parse::().unwrap().is_ascii_alphabetic()); 286 | assert!("abc123" 287 | .parse::() 288 | .unwrap() 289 | .is_ascii_alphanumeric()); 290 | } 291 | 292 | #[test] 293 | fn tiny8_numeric() { 294 | let s: TinyStr8 = "@abcXYZ[".parse().unwrap(); 295 | assert!(!s.is_ascii_numeric()); 296 | 297 | assert!("01234567".parse::().unwrap().is_ascii_numeric()); 298 | } 299 | 300 | #[test] 301 | fn tiny8_titlecase() { 302 | assert_eq!( 303 | "abcdabcd" 304 | .parse::() 305 | .unwrap() 306 | .to_ascii_titlecase() 307 | .as_str(), 308 | "Abcdabcd" 309 | ); 310 | assert_eq!( 311 | "ABCDABCD" 312 | .parse::() 313 | .unwrap() 314 | .to_ascii_titlecase() 315 | .as_str(), 316 | "Abcdabcd" 317 | ); 318 | assert_eq!( 319 | "aBCDaBCD" 320 | .parse::() 321 | .unwrap() 322 | .to_ascii_titlecase() 323 | .as_str(), 324 | "Abcdabcd" 325 | ); 326 | assert_eq!( 327 | "A123a123" 328 | .parse::() 329 | .unwrap() 330 | .to_ascii_titlecase() 331 | .as_str(), 332 | "A123a123" 333 | ); 334 | assert_eq!( 335 | "123a123A" 336 | .parse::() 337 | .unwrap() 338 | .to_ascii_titlecase() 339 | .as_str(), 340 | "123a123a" 341 | ); 342 | } 343 | 344 | #[test] 345 | fn tiny8_ord() { 346 | let mut v: Vec = vec![ 347 | tinystr8!("nedis"), 348 | tinystr8!("macos"), 349 | tinystr8!("zzy"), 350 | tinystr8!("aab"), 351 | ]; 352 | v.sort(); 353 | assert_eq!(Some("aab"), v.get(0).map(TinyStr8::as_str)); 354 | assert_eq!(Some("macos"), v.get(1).map(TinyStr8::as_str)); 355 | assert_eq!(Some("nedis"), v.get(2).map(TinyStr8::as_str)); 356 | assert_eq!(Some("zzy"), v.get(3).map(TinyStr8::as_str)); 357 | } 358 | 359 | /// Test consistency of TinyStr Ord with String 360 | #[test] 361 | fn tinystr8_ord_consistency() { 362 | let mut string_vec = random_alphanums(3..=8, 100); 363 | let mut tinystr_vec: Vec = string_vec.iter().map(|s| s.parse().unwrap()).collect(); 364 | string_vec.sort(); 365 | tinystr_vec.sort(); 366 | assert_eq!( 367 | string_vec, 368 | tinystr_vec 369 | .iter() 370 | .map(|s| s.as_str().to_string()) 371 | .collect::>() 372 | ); 373 | } 374 | 375 | #[test] 376 | fn tiny8_eq() { 377 | let s1: TinyStr8 = "windows".parse().unwrap(); 378 | let s2: TinyStr8 = "mac".parse().unwrap(); 379 | let s3: TinyStr8 = "windows".parse().unwrap(); 380 | 381 | assert_eq!(s1, s3); 382 | assert_ne!(s1, s2); 383 | } 384 | 385 | #[test] 386 | fn tiny8_display() { 387 | let s: TinyStr8 = "abcdef".parse().unwrap(); 388 | let mut result = String::new(); 389 | write!(result, "{}", s).unwrap(); 390 | assert_eq!(result, "abcdef"); 391 | assert_eq!(format!("{}", s), "abcdef"); 392 | assert_eq!(format!("{:>8}", s), format!("{:>8}", result)); 393 | } 394 | 395 | #[test] 396 | fn tiny8_debug() { 397 | let s: TinyStr8 = "abcdef".parse().unwrap(); 398 | assert_eq!(format!("{:#?}", s), "\"abcdef\""); 399 | } 400 | 401 | #[test] 402 | fn tiny16_from_bytes() { 403 | let s = TinyStr16::from_bytes(b"abcdefghijk").unwrap(); 404 | assert_eq!(s.deref(), "abcdefghijk"); 405 | 406 | assert_eq!( 407 | TinyStr16::from_bytes(&[0, 159, 146, 150]), 408 | Err(Error::NonAscii) 409 | ); 410 | assert_eq!(TinyStr16::from_bytes(&[]), Err(Error::InvalidSize)); 411 | assert_eq!(TinyStr16::from_bytes(&[0]), Err(Error::InvalidNull)); 412 | } 413 | 414 | #[test] 415 | fn tiny16_size() { 416 | assert_eq!("".parse::(), Err(Error::InvalidSize)); 417 | assert!("1".parse::().is_ok()); 418 | assert!("12".parse::().is_ok()); 419 | assert!("123".parse::().is_ok()); 420 | assert!("1234".parse::().is_ok()); 421 | assert!("12345".parse::().is_ok()); 422 | assert!("123456".parse::().is_ok()); 423 | assert!("1234567".parse::().is_ok()); 424 | assert!("12345678".parse::().is_ok()); 425 | assert!("123456781".parse::().is_ok()); 426 | assert!("1234567812".parse::().is_ok()); 427 | assert!("12345678123".parse::().is_ok()); 428 | assert!("123456781234".parse::().is_ok()); 429 | assert!("1234567812345".parse::().is_ok()); 430 | assert!("12345678123456".parse::().is_ok()); 431 | assert!("123456781234567".parse::().is_ok()); 432 | assert!("1234567812345678".parse::().is_ok()); 433 | assert_eq!( 434 | "12345678123456789".parse::(), 435 | Err(Error::InvalidSize) 436 | ); 437 | } 438 | 439 | #[test] 440 | fn tiny16_null() { 441 | assert_eq!("a\u{0}b".parse::(), Err(Error::InvalidNull)); 442 | } 443 | 444 | #[test] 445 | fn tiny16_new_unchecked() { 446 | let reference: TinyStr16 = "WindowsCE/ME/NT".parse().unwrap(); 447 | let uval: u128 = reference.into(); 448 | let s = unsafe { TinyStr16::new_unchecked(uval) }; 449 | assert_eq!(s, reference); 450 | assert_eq!(s, "WindowsCE/ME/NT"); 451 | } 452 | 453 | #[test] 454 | fn tiny16_nonascii() { 455 | assert_eq!("\u{4000}".parse::(), Err(Error::NonAscii)); 456 | } 457 | 458 | #[test] 459 | fn tiny16_alpha() { 460 | let s: TinyStr16 = "@abcdefgTUVWXYZ[".parse().unwrap(); 461 | assert!(!s.is_ascii_alphabetic()); 462 | assert!(!s.is_ascii_alphanumeric()); 463 | assert_eq!(s.to_ascii_uppercase().as_str(), "@ABCDEFGTUVWXYZ["); 464 | assert_eq!(s.to_ascii_lowercase().as_str(), "@abcdefgtuvwxyz["); 465 | 466 | assert!("abcdefgTUVWXYZ" 467 | .parse::() 468 | .unwrap() 469 | .is_ascii_alphabetic()); 470 | assert!("abcdefgTUVWXYZ" 471 | .parse::() 472 | .unwrap() 473 | .is_ascii_alphanumeric()); 474 | assert!(!"abcdefg0123456" 475 | .parse::() 476 | .unwrap() 477 | .is_ascii_alphabetic()); 478 | assert!("abcdefgTUVWXYZ" 479 | .parse::() 480 | .unwrap() 481 | .is_ascii_alphanumeric()); 482 | } 483 | 484 | #[test] 485 | fn tiny16_numeric() { 486 | let s: TinyStr16 = "@abcdefgTUVWXYZ[".parse().unwrap(); 487 | assert!(!s.is_ascii_numeric()); 488 | 489 | assert!("0123456789" 490 | .parse::() 491 | .unwrap() 492 | .is_ascii_numeric()); 493 | } 494 | 495 | #[test] 496 | fn tiny16_titlecase() { 497 | assert_eq!( 498 | "abcdabcdabcdabcd" 499 | .parse::() 500 | .unwrap() 501 | .to_ascii_titlecase() 502 | .as_str(), 503 | "Abcdabcdabcdabcd" 504 | ); 505 | assert_eq!( 506 | "ABCDABCDABCDABCD" 507 | .parse::() 508 | .unwrap() 509 | .to_ascii_titlecase() 510 | .as_str(), 511 | "Abcdabcdabcdabcd" 512 | ); 513 | assert_eq!( 514 | "aBCDaBCDaBCDaBCD" 515 | .parse::() 516 | .unwrap() 517 | .to_ascii_titlecase() 518 | .as_str(), 519 | "Abcdabcdabcdabcd" 520 | ); 521 | assert_eq!( 522 | "A123a123A123a123" 523 | .parse::() 524 | .unwrap() 525 | .to_ascii_titlecase() 526 | .as_str(), 527 | "A123a123a123a123" 528 | ); 529 | assert_eq!( 530 | "123a123A123a123A" 531 | .parse::() 532 | .unwrap() 533 | .to_ascii_titlecase() 534 | .as_str(), 535 | "123a123a123a123a" 536 | ); 537 | } 538 | 539 | #[test] 540 | fn tiny16_ord() { 541 | let mut v: Vec = vec![ 542 | tinystr16!("nedis_xxxx"), 543 | tinystr16!("macos_xxxx"), 544 | tinystr16!("xxxxxxxx_b"), 545 | tinystr16!("xxxxxxxx_aa"), 546 | tinystr16!("zzy"), 547 | tinystr16!("aab"), 548 | ]; 549 | v.sort(); 550 | assert_eq!(Some("aab"), v.get(0).map(TinyStr16::as_str)); 551 | assert_eq!(Some("macos_xxxx"), v.get(1).map(TinyStr16::as_str)); 552 | assert_eq!(Some("nedis_xxxx"), v.get(2).map(TinyStr16::as_str)); 553 | assert_eq!(Some("xxxxxxxx_aa"), v.get(3).map(TinyStr16::as_str)); 554 | assert_eq!(Some("xxxxxxxx_b"), v.get(4).map(TinyStr16::as_str)); 555 | assert_eq!(Some("zzy"), v.get(5).map(TinyStr16::as_str)); 556 | } 557 | 558 | /// Test consistency of TinyStr Ord with String 559 | #[test] 560 | fn tinystr16_ord_consistency() { 561 | let mut string_vec = random_alphanums(1..=16, 100); 562 | let mut tinystr_vec: Vec = string_vec.iter().map(|s| s.parse().unwrap()).collect(); 563 | string_vec.sort(); 564 | tinystr_vec.sort(); 565 | assert_eq!( 566 | string_vec, 567 | tinystr_vec 568 | .iter() 569 | .map(|s| s.as_str().to_string()) 570 | .collect::>() 571 | ); 572 | } 573 | 574 | #[test] 575 | fn tiny16_eq() { 576 | let s1: TinyStr16 = "windows98SE".parse().unwrap(); 577 | let s2: TinyStr16 = "mac".parse().unwrap(); 578 | let s3: TinyStr16 = "windows98SE".parse().unwrap(); 579 | 580 | assert_eq!(s1, s3); 581 | assert_ne!(s1, s2); 582 | } 583 | 584 | #[test] 585 | fn tiny16_display() { 586 | let s: TinyStr16 = "abcdefghijkl".parse().unwrap(); 587 | let mut result = String::new(); 588 | write!(result, "{}", s).unwrap(); 589 | assert_eq!(result, "abcdefghijkl"); 590 | assert_eq!(format!("{}", s), "abcdefghijkl"); 591 | assert_eq!(format!("{:>14}", s), format!("{:>14}", result)); 592 | } 593 | 594 | #[test] 595 | fn tiny16_debug() { 596 | let s: TinyStr16 = "abcdefghijkl".parse().unwrap(); 597 | assert_eq!(format!("{:#?}", s), "\"abcdefghijkl\""); 598 | } 599 | 600 | #[cfg(feature = "std")] 601 | #[test] 602 | fn supports_std_error() { 603 | let e = "\u{4000}".parse::().unwrap_err(); 604 | let _: &dyn std::error::Error = &e; 605 | } 606 | 607 | #[cfg(any(feature = "std", feature = "alloc"))] 608 | #[test] 609 | fn tinyauto_basic() { 610 | let s1: TinyStrAuto = "abc".parse().unwrap(); 611 | assert_eq!(s1, "abc"); 612 | 613 | let s2: TinyStrAuto = "veryveryveryveryverylong".parse().unwrap(); 614 | assert_eq!(s2, "veryveryveryveryverylong"); 615 | } 616 | 617 | #[cfg(any(feature = "std", feature = "alloc"))] 618 | #[test] 619 | fn tinyauto_nonascii() { 620 | assert_eq!("\u{4000}".parse::(), Err(Error::NonAscii)); 621 | assert_eq!( 622 | "veryveryveryveryverylong\u{4000}".parse::(), 623 | Err(Error::NonAscii) 624 | ); 625 | } 626 | 627 | #[cfg(feature = "macros")] 628 | const TS: TinyStr8 = tinystr::macros::tinystr8!("test"); 629 | 630 | #[cfg(feature = "macros")] 631 | #[test] 632 | fn tinystr_macros() { 633 | use tinystr::macros::*; 634 | 635 | let x: TinyStr8 = "test".parse().unwrap(); 636 | assert_eq!(TS, x); 637 | 638 | let x: TinyStr4 = "foo".parse().unwrap(); 639 | assert_eq!(tinystr4!("foo"), x); 640 | 641 | let x: TinyStr8 = "barbaz".parse().unwrap(); 642 | assert_eq!(tinystr8!("barbaz"), x); 643 | 644 | let x: TinyStr16 = "metamorphosis".parse().unwrap(); 645 | assert_eq!(tinystr16!("metamorphosis"), x); 646 | } 647 | --------------------------------------------------------------------------------