From 48ff7fa4718660d460ccfa24fd1aec2bde9f17b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1=20de=20Mello?= Date: Sat, 13 Jun 2026 11:20:53 +0100 Subject: [PATCH 1/2] feat: add allocation-free char primitives and no-alloc tier Adds a third feature tier so the crate scales down to no_std targets without an allocator. The existing no_std (alloc) tier from #28 is preserved. - Cargo.toml: add an `alloc` feature gating the Cow-returning encode/decode API; `std` now implies `alloc`, so the default build is unchanged. - New allocation-free per-codepage primitives, available without `alloc`: - `encode_char(c: char) -> Option` - `decode_byte(b: u8) -> char` (complete) / `Option` (incomplete) - gate the Cow API, decode helpers, and table-write paths behind `alloc` across lib.rs, encoder/decoder, and the codegen templates (regenerated code_pages match). - CI: the bare-metal job now also builds the no_std + alloc tier; the no-features build doubles as the no-allocator guard. - docs: document the three tiers; bump version to 1.5.0. BREAKING for no_std users of 1.4.0: `default-features = false` now selects the no-allocator tier. Add `features = ["alloc"]` to keep the full API. --- .github/workflows/nix.yml | 17 ++++- CHANGELOG.md | 5 ++ Cargo.toml | 13 ++-- README.md | 37 +++++++--- .../src/codegen_helper/templates/complete.rs | 60 +++++++++++++++- .../codegen_helper/templates/incomplete.rs | 68 +++++++++++++++++-- src/code_pages/cp1250.rs | 60 +++++++++++++++- src/code_pages/cp1251.rs | 60 +++++++++++++++- src/code_pages/cp1252.rs | 60 +++++++++++++++- src/code_pages/cp1253.rs | 66 ++++++++++++++++-- src/code_pages/cp1254.rs | 60 +++++++++++++++- src/code_pages/cp1255.rs | 66 ++++++++++++++++-- src/code_pages/cp1256.rs | 60 +++++++++++++++- src/code_pages/cp1257.rs | 66 ++++++++++++++++-- src/code_pages/cp1258.rs | 60 +++++++++++++++- src/code_pages/cp437.rs | 60 +++++++++++++++- src/code_pages/cp737.rs | 60 +++++++++++++++- src/code_pages/cp850.rs | 60 +++++++++++++++- src/code_pages/cp852.rs | 60 +++++++++++++++- src/code_pages/cp855.rs | 60 +++++++++++++++- src/code_pages/cp857.rs | 66 ++++++++++++++++-- src/code_pages/cp860.rs | 60 +++++++++++++++- src/code_pages/cp861.rs | 60 +++++++++++++++- src/code_pages/cp862.rs | 60 +++++++++++++++- src/code_pages/cp863.rs | 60 +++++++++++++++- src/code_pages/cp864.rs | 67 ++++++++++++++++-- src/code_pages/cp865.rs | 60 +++++++++++++++- src/code_pages/cp866.rs | 60 +++++++++++++++- src/code_pages/cp869.rs | 66 ++++++++++++++++-- src/code_pages/cp874.rs | 66 ++++++++++++++++-- src/code_pages/cp910.rs | 60 +++++++++++++++- src/decoder.rs | 6 ++ src/decoder/complete.rs | 12 +++- src/decoder/incomplete.rs | 11 +++ src/encoder.rs | 5 ++ src/lib.rs | 45 ++++++++++++ 36 files changed, 1724 insertions(+), 98 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index aa9325c..909ccdf 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -40,10 +40,14 @@ jobs: use-flakehub: false # Cross-compile to a bare-metal target that has no `std` at all, so a - # `no_std` regression cannot slip through. - - name: Build no_std (bare-metal target) + # `no_std` regression cannot slip through. The no-features build also has + # no allocator, so it additionally guards the allocation-free primitives. + - name: Build no_std + no_alloc (bare-metal target) run: nix develop --command cargo build --no-default-features --lib --target thumbv7em-none-eabi --verbose + - name: Build no_std + alloc (bare-metal target) + run: nix develop --command cargo build --no-default-features --features alloc --lib --target thumbv7em-none-eabi --verbose + fuzz: runs-on: ubuntu-latest steps: @@ -99,6 +103,15 @@ jobs: - name: Run clippy run: nix develop --command cargo clippy --all-targets -- -D warnings + # The `#[cfg(feature = "alloc")]` gating means an unused import in a + # reduced feature set can only be caught by linting that set. Benches and + # integration targets are not feature-gated, so these are lib-scoped. + - name: Run clippy (no_std, no allocator) + run: nix develop --command cargo clippy --no-default-features --lib -- -D warnings + + - name: Run clippy (no_std, alloc) + run: nix develop --command cargo clippy --no-default-features --features alloc --lib -- -D warnings + codegen: runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1482aac..12b8499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.0] - 2026-06-13 +- Add allocation-free `encode_char` / `decode_byte` primitives, available without an allocator (e.g. for `no_std` embedded targets). +- Add an `alloc` feature gating the `Cow`-returning `encode`/`decode` API. `std` now implies `alloc`, so the default build is unchanged. +- **Behavior change:** `default-features = false` previously implied an allocator and kept the full API. It now selects the no-allocator tier; add `features = ["alloc"]` to restore the `no_std` + allocator behavior introduced in 1.4.0. + ## [1.4.0] - 2026-06-09 - Add `no_std` support. Disable the default `std` feature (`default-features = false`) to build without the standard library; the crate still requires an allocator. The only difference is that the `std::error::Error` impls for `EncodeError`/`DecodeError` are omitted. No changes for existing users. diff --git a/Cargo.toml b/Cargo.toml index 9eeb468..9ce30b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yore" -version = "1.4.0" +version = "1.5.0" authors = ["Andreas Liljeqvist "] edition = "2021" categories = ["encoding"] @@ -13,10 +13,13 @@ repository = "https://github.com/bonega/yore/" [features] default = ["std"] -# Enables `std::error::Error` impls for the error types. -# Disable (`--no-default-features`) to build as `no_std`; the crate still -# requires an allocator (it uses the `alloc` crate). -std = [] +# Enables `std::error::Error` impls for the error types. Implies `alloc`. +std = ["alloc"] +# Enables the allocating API (the `Cow`-returning `encode`/`decode` family), +# pulling in the `alloc` crate. Disable (`--no-default-features`) to build for +# `no_std` targets without an allocator; only the allocation-free char +# primitives (`encode_char`, `decode_byte`) remain available. +alloc = [] [dependencies] diff --git a/README.md b/README.md index 626d034..23744df 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Rust library for decoding and encoding character sets based on OEM code pages. -[![yore at crates.io](https://img.shields.io/badge/crates.io-1.4.0-blue)](https://crates.io/crates/yore) +[![yore at crates.io](https://img.shields.io/badge/crates.io-1.5.0-blue)](https://crates.io/crates/yore) [![yore at docs.rs](https://docs.rs/yore/badge.svg)](https://docs.rs/yore) # Features @@ -11,7 +11,8 @@ A Rust library for decoding and encoding character sets based on OEM code pages. * Easy-to-use API * Broad range of [supported code pages](#supported-code-pages) * Handles code pages with redefined ASCII characters (<0x80), such as '٪' in CP864 -* `no_std` support (requires an allocator) by disabling default features +* `no_std` support, with or without an allocator, via cargo features +* Allocation-free `encode_char` / `decode_byte` primitives for embedded use # Usage @@ -19,22 +20,40 @@ Add `yore` to your `Cargo.toml` file. ```toml [dependencies] -yore = "1.4.0" +yore = "1.5.0" ``` ## `no_std` -`yore` builds without the standard library when the default `std` feature is -disabled. It still requires an allocator, since the API returns owned `Cow` -buffers. +`yore` has three feature tiers, so it scales from std down to bare-metal targets +with no allocator: + +| Cargo features | Environment | API | +| --- | --- | --- | +| `std` (default) | `std` | Full API + `std::error::Error` impls | +| `alloc` | `no_std` + allocator | Full API; `Error` impls omitted (`Display` stays) | +| *(none)* | `no_std`, no allocator | Allocation-free char primitives only | + +The allocating `encode`/`decode` family returns owned `Cow` buffers and so +requires the `alloc` feature. Without it, only the allocation-free +`encode_char` / `decode_byte` primitives are available: ```toml [dependencies] -yore = { version = "1.4.0", default-features = false } +# no_std with an allocator: keep the full Cow-returning API +yore = { version = "1.5.0", default-features = false, features = ["alloc"] } + +# no_std without an allocator: char primitives only +yore = { version = "1.5.0", default-features = false } ``` -The only difference from the default build is that the `std::error::Error` impls -for `EncodeError`/`DecodeError` are omitted (`Display` is always available). +```rust +use yore::code_pages::CP850; + +// Encode/decode one character at a time, no allocation required. +assert_eq!(CP850.encode_char('A'), Some(b'A')); +assert_eq!(CP850.decode_byte(b'A'), 'A'); +``` # Examples diff --git a/codegen/src/codegen_helper/templates/complete.rs b/codegen/src/codegen_helper/templates/complete.rs index 2a4d96e..fed2eb0 100644 --- a/codegen/src/codegen_helper/templates/complete.rs +++ b/codegen/src/codegen_helper/templates/complete.rs @@ -1,10 +1,18 @@ +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CODERSTRUCT; @@ -18,11 +26,35 @@ impl CODERSTRUCT { /// /// assert_eq!(CODERSTRUCT.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CODERSTRUCT byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CODERSTRUCT + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CODERSTRUCT; + /// + /// assert_eq!(CODERSTRUCT.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CODERSTRUCT byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -36,6 +68,7 @@ impl CODERSTRUCT { /// assert_eq!(CODERSTRUCT.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CODERSTRUCT.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -52,12 +85,35 @@ impl CODERSTRUCT { /// /// assert_eq!(CODERSTRUCT.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CODERSTRUCT byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CODERSTRUCT; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CODERSTRUCT.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CODERSTRUCT { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/codegen/src/codegen_helper/templates/incomplete.rs b/codegen/src/codegen_helper/templates/incomplete.rs index 7de5f12..f0dbfdd 100644 --- a/codegen/src/codegen_helper/templates/incomplete.rs +++ b/codegen/src/codegen_helper/templates/incomplete.rs @@ -1,16 +1,20 @@ +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, - complete::decode_helper as decode_helper_lossy, - incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, +}; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CODERSTRUCT { /// Decode CODERSTRUCT byte-encoding into UTF-8 string /// @@ -23,6 +27,7 @@ impl CODERSTRUCT { /// /// assert_eq!(CODERSTRUCT.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +44,7 @@ impl CODERSTRUCT { /// /// assert_eq!(CODERSTRUCT.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +64,32 @@ impl CODERSTRUCT { /// /// assert_eq!(CODERSTRUCT.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CODERSTRUCT byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CODERSTRUCT; + /// + /// assert_eq!(CODERSTRUCT.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CODERSTRUCT byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +103,7 @@ impl CODERSTRUCT { /// assert_eq!(CODERSTRUCT.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CODERSTRUCT.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +120,45 @@ impl CODERSTRUCT { /// /// assert_eq!(CODERSTRUCT.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CODERSTRUCT byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CODERSTRUCT; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CODERSTRUCT.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CODERSTRUCT; impl CodePage for CODERSTRUCT { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +167,7 @@ impl CodePage for CODERSTRUCT { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -122,4 +175,5 @@ impl CodePage for CODERSTRUCT { } const DECODE_TABLE: decoder::incomplete::Table = PLACEHOLDER_TABLE; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = PLACEHOLDER_LOSSY_TABLE; diff --git a/src/code_pages/cp1250.rs b/src/code_pages/cp1250.rs index d775c9a..335922a 100644 --- a/src/code_pages/cp1250.rs +++ b/src/code_pages/cp1250.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP1250; @@ -20,11 +28,35 @@ impl CP1250 { /// /// assert_eq!(CP1250.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP1250 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP1250 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1250; + /// + /// assert_eq!(CP1250.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP1250 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP1250 { /// assert_eq!(CP1250.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1250.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP1250 { /// /// assert_eq!(CP1250.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1250 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1250; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1250.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP1250 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp1251.rs b/src/code_pages/cp1251.rs index 630e72f..a688bd3 100644 --- a/src/code_pages/cp1251.rs +++ b/src/code_pages/cp1251.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP1251; @@ -20,11 +28,35 @@ impl CP1251 { /// /// assert_eq!(CP1251.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP1251 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP1251 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1251; + /// + /// assert_eq!(CP1251.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP1251 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP1251 { /// assert_eq!(CP1251.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1251.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP1251 { /// /// assert_eq!(CP1251.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1251 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1251; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1251.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP1251 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp1252.rs b/src/code_pages/cp1252.rs index 8815180..4b7f326 100644 --- a/src/code_pages/cp1252.rs +++ b/src/code_pages/cp1252.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP1252; @@ -20,11 +28,35 @@ impl CP1252 { /// /// assert_eq!(CP1252.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP1252 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP1252 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1252; + /// + /// assert_eq!(CP1252.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP1252 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP1252 { /// assert_eq!(CP1252.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1252.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP1252 { /// /// assert_eq!(CP1252.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1252 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1252; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1252.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP1252 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp1253.rs b/src/code_pages/cp1253.rs index 2f9ee45..634222a 100644 --- a/src/code_pages/cp1253.rs +++ b/src/code_pages/cp1253.rs @@ -1,16 +1,22 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP1253 { /// Decode CP1253 byte-encoding into UTF-8 string /// @@ -23,6 +29,7 @@ impl CP1253 { /// /// assert_eq!(CP1253.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +46,7 @@ impl CP1253 { /// /// assert_eq!(CP1253.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +66,32 @@ impl CP1253 { /// /// assert_eq!(CP1253.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP1253 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1253; + /// + /// assert_eq!(CP1253.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP1253 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +105,7 @@ impl CP1253 { /// assert_eq!(CP1253.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1253.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +122,45 @@ impl CP1253 { /// /// assert_eq!(CP1253.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1253 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1253; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1253.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP1253; impl CodePage for CP1253 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +169,7 @@ impl CodePage for CP1253 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1138,6 +1193,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ }), None, ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp1254.rs b/src/code_pages/cp1254.rs index 33aba41..b52aa12 100644 --- a/src/code_pages/cp1254.rs +++ b/src/code_pages/cp1254.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP1254; @@ -20,11 +28,35 @@ impl CP1254 { /// /// assert_eq!(CP1254.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP1254 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP1254 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1254; + /// + /// assert_eq!(CP1254.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP1254 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP1254 { /// assert_eq!(CP1254.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1254.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP1254 { /// /// assert_eq!(CP1254.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1254 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1254; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1254.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP1254 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp1255.rs b/src/code_pages/cp1255.rs index fa95be2..657c170 100644 --- a/src/code_pages/cp1255.rs +++ b/src/code_pages/cp1255.rs @@ -1,16 +1,22 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP1255 { /// Decode CP1255 byte-encoding into UTF-8 string /// @@ -23,6 +29,7 @@ impl CP1255 { /// /// assert_eq!(CP1255.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +46,7 @@ impl CP1255 { /// /// assert_eq!(CP1255.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +66,32 @@ impl CP1255 { /// /// assert_eq!(CP1255.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP1255 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1255; + /// + /// assert_eq!(CP1255.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP1255 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +105,7 @@ impl CP1255 { /// assert_eq!(CP1255.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1255.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +122,45 @@ impl CP1255 { /// /// assert_eq!(CP1255.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1255 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1255; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1255.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP1255; impl CodePage for CP1255 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +169,7 @@ impl CodePage for CP1255 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1117,6 +1172,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ }), None, ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp1256.rs b/src/code_pages/cp1256.rs index 09b4c68..f3a789a 100644 --- a/src/code_pages/cp1256.rs +++ b/src/code_pages/cp1256.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP1256; @@ -20,11 +28,35 @@ impl CP1256 { /// /// assert_eq!(CP1256.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP1256 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP1256 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1256; + /// + /// assert_eq!(CP1256.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP1256 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP1256 { /// assert_eq!(CP1256.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1256.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP1256 { /// /// assert_eq!(CP1256.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1256 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1256; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1256.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP1256 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp1257.rs b/src/code_pages/cp1257.rs index 00dc71a..7ea2d2f 100644 --- a/src/code_pages/cp1257.rs +++ b/src/code_pages/cp1257.rs @@ -1,16 +1,22 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP1257 { /// Decode CP1257 byte-encoding into UTF-8 string /// @@ -23,6 +29,7 @@ impl CP1257 { /// /// assert_eq!(CP1257.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +46,7 @@ impl CP1257 { /// /// assert_eq!(CP1257.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +66,32 @@ impl CP1257 { /// /// assert_eq!(CP1257.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP1257 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1257; + /// + /// assert_eq!(CP1257.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP1257 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +105,7 @@ impl CP1257 { /// assert_eq!(CP1257.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1257.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +122,45 @@ impl CP1257 { /// /// assert_eq!(CP1257.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1257 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1257; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1257.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP1257; impl CodePage for CP1257 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +169,7 @@ impl CodePage for CP1257 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1141,6 +1196,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ len: IncompleteLen::Two, }), ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp1258.rs b/src/code_pages/cp1258.rs index c7e5bb1..abfbf67 100644 --- a/src/code_pages/cp1258.rs +++ b/src/code_pages/cp1258.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP1258; @@ -20,11 +28,35 @@ impl CP1258 { /// /// assert_eq!(CP1258.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP1258 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP1258 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP1258; + /// + /// assert_eq!(CP1258.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP1258 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP1258 { /// assert_eq!(CP1258.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP1258.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP1258 { /// /// assert_eq!(CP1258.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP1258 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP1258; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP1258.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP1258 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp437.rs b/src/code_pages/cp437.rs index 87321ed..e546bf9 100644 --- a/src/code_pages/cp437.rs +++ b/src/code_pages/cp437.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP437; @@ -20,11 +28,35 @@ impl CP437 { /// /// assert_eq!(CP437.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP437 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP437 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP437; + /// + /// assert_eq!(CP437.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP437 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP437 { /// assert_eq!(CP437.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP437.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP437 { /// /// assert_eq!(CP437.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP437 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP437; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP437.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP437 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp737.rs b/src/code_pages/cp737.rs index 2971ad3..769c81b 100644 --- a/src/code_pages/cp737.rs +++ b/src/code_pages/cp737.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP737; @@ -20,11 +28,35 @@ impl CP737 { /// /// assert_eq!(CP737.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP737 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP737 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP737; + /// + /// assert_eq!(CP737.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP737 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP737 { /// assert_eq!(CP737.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP737.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP737 { /// /// assert_eq!(CP737.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP737 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP737; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP737.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP737 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp850.rs b/src/code_pages/cp850.rs index d4d4f13..9749c6d 100644 --- a/src/code_pages/cp850.rs +++ b/src/code_pages/cp850.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP850; @@ -20,11 +28,35 @@ impl CP850 { /// /// assert_eq!(CP850.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP850 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP850 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP850; + /// + /// assert_eq!(CP850.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP850 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP850 { /// assert_eq!(CP850.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP850.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP850 { /// /// assert_eq!(CP850.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP850 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP850; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP850.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP850 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp852.rs b/src/code_pages/cp852.rs index 9a32607..4bc9a3c 100644 --- a/src/code_pages/cp852.rs +++ b/src/code_pages/cp852.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP852; @@ -20,11 +28,35 @@ impl CP852 { /// /// assert_eq!(CP852.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP852 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP852 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP852; + /// + /// assert_eq!(CP852.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP852 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP852 { /// assert_eq!(CP852.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP852.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP852 { /// /// assert_eq!(CP852.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP852 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP852; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP852.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP852 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp855.rs b/src/code_pages/cp855.rs index f84d6b4..772e427 100644 --- a/src/code_pages/cp855.rs +++ b/src/code_pages/cp855.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP855; @@ -20,11 +28,35 @@ impl CP855 { /// /// assert_eq!(CP855.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP855 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP855 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP855; + /// + /// assert_eq!(CP855.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP855 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP855 { /// assert_eq!(CP855.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP855.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP855 { /// /// assert_eq!(CP855.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP855 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP855; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP855.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP855 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp857.rs b/src/code_pages/cp857.rs index 533ed60..f2b1cb0 100644 --- a/src/code_pages/cp857.rs +++ b/src/code_pages/cp857.rs @@ -1,16 +1,22 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP857 { /// Decode CP857 byte-encoding into UTF-8 string /// @@ -23,6 +29,7 @@ impl CP857 { /// /// assert_eq!(CP857.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +46,7 @@ impl CP857 { /// /// assert_eq!(CP857.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +66,32 @@ impl CP857 { /// /// assert_eq!(CP857.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP857 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP857; + /// + /// assert_eq!(CP857.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP857 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +105,7 @@ impl CP857 { /// assert_eq!(CP857.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP857.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +122,45 @@ impl CP857 { /// /// assert_eq!(CP857.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP857 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP857; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP857.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP857; impl CodePage for CP857 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +169,7 @@ impl CodePage for CP857 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1138,6 +1193,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ len: IncompleteLen::Two, }), ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp860.rs b/src/code_pages/cp860.rs index 6f73473..ab36d4e 100644 --- a/src/code_pages/cp860.rs +++ b/src/code_pages/cp860.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP860; @@ -20,11 +28,35 @@ impl CP860 { /// /// assert_eq!(CP860.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP860 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP860 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP860; + /// + /// assert_eq!(CP860.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP860 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP860 { /// assert_eq!(CP860.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP860.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP860 { /// /// assert_eq!(CP860.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP860 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP860; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP860.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP860 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp861.rs b/src/code_pages/cp861.rs index cca452d..26e74e8 100644 --- a/src/code_pages/cp861.rs +++ b/src/code_pages/cp861.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP861; @@ -20,11 +28,35 @@ impl CP861 { /// /// assert_eq!(CP861.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP861 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP861 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP861; + /// + /// assert_eq!(CP861.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP861 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP861 { /// assert_eq!(CP861.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP861.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP861 { /// /// assert_eq!(CP861.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP861 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP861; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP861.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP861 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp862.rs b/src/code_pages/cp862.rs index 75b048c..eeaa1e5 100644 --- a/src/code_pages/cp862.rs +++ b/src/code_pages/cp862.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP862; @@ -20,11 +28,35 @@ impl CP862 { /// /// assert_eq!(CP862.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP862 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP862 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP862; + /// + /// assert_eq!(CP862.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP862 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP862 { /// assert_eq!(CP862.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP862.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP862 { /// /// assert_eq!(CP862.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP862 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP862; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP862.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP862 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp863.rs b/src/code_pages/cp863.rs index ac30cef..51343e6 100644 --- a/src/code_pages/cp863.rs +++ b/src/code_pages/cp863.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP863; @@ -20,11 +28,35 @@ impl CP863 { /// /// assert_eq!(CP863.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP863 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP863 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP863; + /// + /// assert_eq!(CP863.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP863 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP863 { /// assert_eq!(CP863.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP863.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP863 { /// /// assert_eq!(CP863.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP863 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP863; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP863.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP863 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp864.rs b/src/code_pages/cp864.rs index c53253f..6779465 100644 --- a/src/code_pages/cp864.rs +++ b/src/code_pages/cp864.rs @@ -1,16 +1,23 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper_non_ascii as decode_helper_non_ascii_lossy, - incomplete::decode_helper_non_ascii, CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper_non_ascii as decode_helper_non_ascii_lossy, + incomplete::decode_helper_non_ascii, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP864 { /// Decode CP864 byte-encoding into UTF-8 string /// @@ -23,6 +30,7 @@ impl CP864 { /// /// assert_eq!(CP864.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper_non_ascii(&DECODE_TABLE, bytes, None) @@ -39,6 +47,7 @@ impl CP864 { /// /// assert_eq!(CP864.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_non_ascii_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +67,32 @@ impl CP864 { /// /// assert_eq!(CP864.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper_non_ascii(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP864 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP864; + /// + /// assert_eq!(CP864.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP864 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +106,7 @@ impl CP864 { /// assert_eq!(CP864.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP864.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +123,45 @@ impl CP864 { /// /// assert_eq!(CP864.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP864 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP864; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP864.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP864; impl CodePage for CP864 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +170,7 @@ impl CodePage for CP864 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1129,6 +1185,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ }), None, ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp865.rs b/src/code_pages/cp865.rs index a93f68c..431d5b6 100644 --- a/src/code_pages/cp865.rs +++ b/src/code_pages/cp865.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP865; @@ -20,11 +28,35 @@ impl CP865 { /// /// assert_eq!(CP865.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP865 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP865 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP865; + /// + /// assert_eq!(CP865.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP865 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP865 { /// assert_eq!(CP865.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP865.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP865 { /// /// assert_eq!(CP865.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP865 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP865; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP865.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP865 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp866.rs b/src/code_pages/cp866.rs index fa2e6e9..c848aa3 100644 --- a/src/code_pages/cp866.rs +++ b/src/code_pages/cp866.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP866; @@ -20,11 +28,35 @@ impl CP866 { /// /// assert_eq!(CP866.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes) } + /// Decode a single CP866 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP866 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP866; + /// + /// assert_eq!(CP866.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP866 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP866 { /// assert_eq!(CP866.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP866.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP866 { /// /// assert_eq!(CP866.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP866 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP866; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP866.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP866 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/code_pages/cp869.rs b/src/code_pages/cp869.rs index 74eaed0..7747c64 100644 --- a/src/code_pages/cp869.rs +++ b/src/code_pages/cp869.rs @@ -1,16 +1,22 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP869 { /// Decode CP869 byte-encoding into UTF-8 string /// @@ -23,6 +29,7 @@ impl CP869 { /// /// assert_eq!(CP869.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +46,7 @@ impl CP869 { /// /// assert_eq!(CP869.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +66,32 @@ impl CP869 { /// /// assert_eq!(CP869.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP869 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP869; + /// + /// assert_eq!(CP869.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP869 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +105,7 @@ impl CP869 { /// assert_eq!(CP869.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP869.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +122,45 @@ impl CP869 { /// /// assert_eq!(CP869.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP869 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP869; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP869.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP869; impl CodePage for CP869 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +169,7 @@ impl CodePage for CP869 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1120,6 +1175,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ len: IncompleteLen::Two, }), ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp874.rs b/src/code_pages/cp874.rs index 5a84ab6..0acaab9 100644 --- a/src/code_pages/cp874.rs +++ b/src/code_pages/cp874.rs @@ -1,16 +1,22 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{ - self, complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, - CompleteEntry, IncompleteEntry, IncompleteLen, - }, + decoder::{self, IncompleteEntry, IncompleteLen}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, +}; + +#[cfg(feature = "alloc")] +use crate::decoder::{ + complete::decode_helper as decode_helper_lossy, incomplete::decode_helper, CompleteEntry, }; +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + impl CP874 { /// Decode CP874 byte-encoding into UTF-8 string /// @@ -23,6 +29,7 @@ impl CP874 { /// /// assert_eq!(CP874.decode(&[116, 101, 120, 116]).unwrap(), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Result, DecodeError> { decode_helper(&DECODE_TABLE, bytes, None) @@ -39,6 +46,7 @@ impl CP874 { /// /// assert_eq!(CP874.decode_lossy(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_lossy(&DECODE_TABLE_LOSSY, bytes) @@ -58,11 +66,32 @@ impl CP874 { /// /// assert_eq!(CP874.decode_lossy_fallback(&[116, 101, 120, 116], '�'), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode_lossy_fallback(self, bytes: &[u8], fallback: char) -> Cow<'_, str> { decode_helper(&DECODE_TABLE, bytes, Some(fallback)).unwrap() } + /// Decode a single CP874 byte into its character. + /// + /// Returns `None` for an undefined codepoint. Allocation-free, so available + /// without the `alloc` feature. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP874; + /// + /// assert_eq!(CP874.decode_byte(b't'), Some('t')); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> Option { + let entry = DECODE_TABLE[b as usize]?; + // SAFETY: table contents are valid UTF-8 by construction. + let s = unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) }; + s.chars().next() + } + /// Encode UTF-8 string into CP874 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -76,6 +105,7 @@ impl CP874 { /// assert_eq!(CP874.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP874.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -92,21 +122,45 @@ impl CP874 { /// /// assert_eq!(CP874.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP874 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP874; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP874.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } #[derive(Copy, Clone)] pub struct CP874; impl CodePage for CP874 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { (*self).decode(bytes) } + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { (*self).decode_lossy(bytes) @@ -115,6 +169,7 @@ impl CodePage for CP874 { /// Note that the `fallback` char should be less than 4 bytes in UTF8. /// 4 bytes UTF8 will panic because of assertion. /// Refrain from using emojis as fallback + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], fallback: char) -> Cow<'a, str> { (*self).decode_lossy_fallback(bytes, fallback) @@ -1123,6 +1178,7 @@ const DECODE_TABLE: decoder::incomplete::Table = [ None, None, ]; +#[cfg(feature = "alloc")] const DECODE_TABLE_LOSSY: decoder::complete::Table = [ CompleteEntry { buf: [0x00, 0x00, 0x00], diff --git a/src/code_pages/cp910.rs b/src/code_pages/cp910.rs index 7b89d5c..7402290 100644 --- a/src/code_pages/cp910.rs +++ b/src/code_pages/cp910.rs @@ -1,12 +1,20 @@ //! Code autogenerated from //! See binary codegen crate +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use crate::{ - decoder::{self, complete::decode_helper_non_ascii, CompleteEntry}, + decoder::{self, CompleteEntry}, encoder::Encoder, - CodePage, DecodeError, EncodeError, + CodePage, }; + +#[cfg(feature = "alloc")] +use crate::decoder::complete::decode_helper_non_ascii; + +#[cfg(feature = "alloc")] +use crate::{DecodeError, EncodeError}; + #[derive(Copy, Clone)] pub struct CP910; @@ -20,11 +28,35 @@ impl CP910 { /// /// assert_eq!(CP910.decode(&[116, 101, 120, 116]), "text"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn decode(self, bytes: &[u8]) -> Cow<'_, str> { decode_helper_non_ascii(&DECODE_TABLE, bytes) } + /// Decode a single CP910 byte into its character. + /// + /// Allocation-free, so available without the `alloc` feature. CP910 + /// is a complete codepage (every byte maps to a character), so this is + /// infallible and returns `char` directly. + /// + /// # Examples + /// + /// ``` + /// use yore::code_pages::CP910; + /// + /// assert_eq!(CP910.decode_byte(b't'), 't'); + /// ``` + #[inline(always)] + pub fn decode_byte(self, b: u8) -> char { + let entry = DECODE_TABLE[b as usize]; + // SAFETY: table contents are valid UTF-8 by construction. + unsafe { core::str::from_utf8_unchecked(&entry.buf[..entry.len as usize]) } + .chars() + .next() + .unwrap() + } + /// Encode UTF-8 string into CP910 byte-encoding /// /// Undefined characters will result in [`EncodeError`] @@ -38,6 +70,7 @@ impl CP910 { /// assert_eq!(CP910.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(CP910.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode(self, s: &str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -54,12 +87,35 @@ impl CP910 { /// /// assert_eq!(CP910.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] pub fn encode_lossy(self, s: &str, fallback: u8) -> Cow<'_, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() } + + /// Encode a single Unicode `char` into its CP910 byte. + /// + /// Returns `None` if the character has no mapping. Allocation-free, so + /// available without the `alloc` feature; compose with `s.chars()` for + /// streaming use: + /// + /// ``` + /// use yore::code_pages::CP910; + /// + /// let s = "text"; + /// let bytes: Vec = s.chars().map(|c| CP910.encode_char(c).unwrap()).collect(); + /// assert_eq!(bytes, vec![116, 101, 120, 116]); + /// ``` + #[inline] + pub fn encode_char(self, c: char) -> Option { + let mut buf = [0u8; 4]; + let utf8 = c.encode_utf8(&mut buf).as_bytes(); + let mut slice: &[u8] = utf8; + self.encode_grapheme(&mut slice) + } } impl CodePage for CP910 { + #[cfg(feature = "alloc")] #[inline(always)] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError> { Ok((*self).decode(bytes)) diff --git a/src/decoder.rs b/src/decoder.rs index 44dbc8e..392ef3a 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -1,13 +1,17 @@ pub(crate) mod complete; pub(crate) mod incomplete; +#[cfg(feature = "alloc")] use alloc::string::String; +#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] use core::mem; pub(crate) use complete::Entry as CompleteEntry; pub(crate) use incomplete::{Entry as IncompleteEntry, Len as IncompleteLen}; +#[cfg(feature = "alloc")] const USIZE_SIZE: usize = mem::size_of::(); /// Given [`buffer`] and end-ptr [`ptr`] set new length and shrink allocation @@ -15,6 +19,7 @@ const USIZE_SIZE: usize = mem::size_of::(); /// # Safety /// /// [`dst`] must be within allocated capacity of [`res`] +#[cfg(feature = "alloc")] #[inline] unsafe fn finalize_string(mut buffer: Vec, dst: *const u8) -> String { let length = dst.offset_from(buffer.as_ptr()) as usize; @@ -24,6 +29,7 @@ unsafe fn finalize_string(mut buffer: Vec, dst: *const u8) -> String { } //lifted from std internal +#[cfg(feature = "alloc")] #[inline] fn contains_nonascii(v: usize) -> bool { const NONASCII_MASK: usize = 0x8080_8080_8080_8080_u64 as usize; diff --git a/src/decoder/complete.rs b/src/decoder/complete.rs index 5ab8690..85a8241 100644 --- a/src/decoder/complete.rs +++ b/src/decoder/complete.rs @@ -1,7 +1,11 @@ +#[cfg(feature = "alloc")] use alloc::borrow::Cow; +#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] use core::mem; +#[cfg(feature = "alloc")] use super::{contains_nonascii, finalize_string, USIZE_SIZE}; /// Entry for complete/lossy tables - optimized for branchless 4-byte writes @@ -20,6 +24,7 @@ impl Entry { /// /// dst must have at least four bytes of space remaining. /// After execution dst will be advanced by the number of bytes written. + #[cfg(feature = "alloc")] #[inline] pub unsafe fn write(self, dst: &mut *mut u8) { let word: u32 = mem::transmute(self); @@ -30,6 +35,7 @@ impl Entry { pub(crate) type Table = [Entry; 256]; +#[cfg(feature = "alloc")] #[inline(always)] pub(crate) fn decode_helper<'a>(table: &Table, src: &'a [u8]) -> Cow<'a, str> { if src.is_ascii() { @@ -39,7 +45,7 @@ pub(crate) fn decode_helper<'a>(table: &Table, src: &'a [u8]) -> Cow<'a, str> { // +1 for branchless 4-byte write which may overshoot by 1 byte let mut buffer: Vec = Vec::with_capacity(src.len() * 3 + 1); - // Safety: decode_slice expects buffer.len() >= src.len() * 3 + // SAFETY: decode_slice expects buffer.len() >= src.len() * 3 let mut dst = buffer.as_mut_ptr(); // If we wouldn't gain anything from the word-at-a-time implementation, fall @@ -75,11 +81,12 @@ pub(crate) fn decode_helper<'a>(table: &Table, src: &'a [u8]) -> Cow<'a, str> { /// Same as `decode_helper`, but have no optimizations for ascii. /// Needed by CP864 and EBCDIC codepages. +#[cfg(feature = "alloc")] #[inline(always)] pub(crate) fn decode_helper_non_ascii<'a>(table: &Table, bytes: &'a [u8]) -> Cow<'a, str> { // +1 for branchless 4-byte write which may overshoot by 1 byte let mut buffer: Vec = Vec::with_capacity(bytes.len() * 3 + 1); - // Safety: decode_slice expects buffer.len() >= src.len() * 3 + // SAFETY: decode_slice expects buffer.len() >= src.len() * 3 let mut dst = buffer.as_mut_ptr(); unsafe { decode_slice(table, bytes, &mut dst) }; unsafe { finalize_string(buffer, dst) }.into() @@ -89,6 +96,7 @@ pub(crate) fn decode_helper_non_ascii<'a>(table: &Table, bytes: &'a [u8]) -> Cow /// # Safety /// /// This function is unsafe because it assumes that the buffer pointed to by [`dst`] has a length >= src.len() * 3 +#[cfg(feature = "alloc")] #[inline] unsafe fn decode_slice(table: &Table, src: &[u8], dst: &mut *mut u8) { for b in src { diff --git a/src/decoder/incomplete.rs b/src/decoder/incomplete.rs index 6790753..67fc1cf 100644 --- a/src/decoder/incomplete.rs +++ b/src/decoder/incomplete.rs @@ -1,9 +1,14 @@ +#[cfg(feature = "alloc")] use alloc::borrow::Cow; +#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] use core::mem; +#[cfg(feature = "alloc")] use crate::DecodeError; +#[cfg(feature = "alloc")] use super::{contains_nonascii, finalize_string, USIZE_SIZE}; /// UTF8 length enum for incomplete tables (enables niche optimization with Option) @@ -23,6 +28,7 @@ pub struct Entry { } impl Entry { + #[cfg(feature = "alloc")] pub fn from_char(c: char) -> Self { let c_len = c.len_utf8(); assert!(c_len < 4); @@ -43,6 +49,7 @@ impl Entry { /// /// dst must have at least three bytes of space remaining. /// After execution dst will be advanced by the number of bytes written. + #[cfg(feature = "alloc")] #[inline] pub unsafe fn write(self, dst: &mut *mut u8) { // Always copy 3 bytes (branchless), then advance by actual length @@ -54,6 +61,7 @@ impl Entry { /// Table for incomplete codepages using Option for niche optimization pub(crate) type Table = [Option; 256]; +#[cfg(feature = "alloc")] #[inline(always)] pub(crate) fn decode_helper<'a>( table: &Table, @@ -110,6 +118,7 @@ pub(crate) fn decode_helper<'a>( /// Same as `decode_helper`, but have no optimizations for ascii. /// Needed by CP864 and EBCDIC codepages. +#[cfg(feature = "alloc")] #[inline(always)] pub(crate) fn decode_helper_non_ascii<'a>( table: &Table, @@ -126,6 +135,7 @@ pub(crate) fn decode_helper_non_ascii<'a>( /// Decode bytes using table lookup. ASCII_OPT enables fast path for ASCII bytes. /// # Safety /// `dst` must point to a buffer with at least `src.len() * 3` bytes of writable space remaining. +#[cfg(feature = "alloc")] #[inline] unsafe fn decode_slice_inner( table: &Table, @@ -151,6 +161,7 @@ unsafe fn decode_slice_inner( Ok(()) } +#[cfg(feature = "alloc")] #[inline] unsafe fn decode_slice( table: &Table, diff --git a/src/encoder.rs b/src/encoder.rs index 4a46a53..b25328f 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -1,10 +1,15 @@ +#[cfg(feature = "alloc")] use alloc::borrow::Cow; +#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] use crate::EncodeError; pub trait Encoder { fn encode_grapheme(&self, bytes: &mut &[u8]) -> Option; + + #[cfg(feature = "alloc")] #[doc(hidden)] #[inline(always)] fn encode_helper<'a>( diff --git a/src/lib.rs b/src/lib.rs index 2fb0d1d..5eb9962 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "alloc")] use alloc::borrow::Cow; use core::fmt; @@ -37,6 +39,7 @@ pub trait CodePage: Encoder { /// assert_eq!(cp850.encode("text").unwrap(), vec![116, 101, 120, 116]); /// assert!(matches!(cp850.encode("text 🦀"), EncodeError)); /// ``` + #[cfg(feature = "alloc")] #[inline] fn encode<'a>(&self, s: &'a str) -> Result, EncodeError> { self.encode_helper(s, None) @@ -55,6 +58,7 @@ pub trait CodePage: Encoder { /// let cp850: &dyn CodePage = &yore::code_pages::CP850; /// assert_eq!(cp850.encode_lossy("text 🦀", 168), vec![116, 101, 120, 116, 32, 168]) /// ``` + #[cfg(feature = "alloc")] #[inline] fn encode_lossy<'a>(&self, s: &'a str, fallback: u8) -> Cow<'a, [u8]> { self.encode_helper(s, Some(fallback)).unwrap() @@ -77,6 +81,7 @@ pub trait CodePage: Encoder { /// //codepoint 231 is undefined /// assert!(matches!(cp857.decode(&[116, 101, 120, 116, 231]), Err(DecodeError{position: 4, value: 231}))); /// ``` + #[cfg(feature = "alloc")] fn decode<'a>(&self, bytes: &'a [u8]) -> Result, DecodeError>; /// Decode single-byte encoding into UTF-8 string @@ -93,6 +98,7 @@ pub trait CodePage: Encoder { /// //codepoint 231 is undefined /// assert_eq!(cp857.decode_lossy(&[116, 101, 120, 116, 32, 231]), "text �"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy<'a>(&self, bytes: &'a [u8]) -> Cow<'a, str> { self.decode(bytes).unwrap() @@ -112,6 +118,7 @@ pub trait CodePage: Encoder { /// //codepoint 231 is undefined /// assert_eq!(cp857.decode_lossy_fallback(&[116, 101, 120, 116, 32, 231], '�'), "text �"); /// ``` + #[cfg(feature = "alloc")] #[inline(always)] fn decode_lossy_fallback<'a>(&self, bytes: &'a [u8], _fallback: char) -> Cow<'a, str> { self.decode(bytes).unwrap() @@ -138,6 +145,44 @@ impl fmt::Display for DecodeError { impl std::error::Error for DecodeError {} #[cfg(test)] +mod no_alloc_tests { + use crate::code_pages::{CP437, CP864}; + + #[test] + fn encode_char_ascii() { + assert_eq!(CP437.encode_char('t'), Some(b't')); + assert_eq!(CP437.encode_char('\n'), Some(b'\n')); + } + + #[test] + fn encode_char_high_glyph() { + assert_eq!(CP437.encode_char('█'), Some(0xDB)); + assert_eq!(CP437.encode_char('╔'), Some(0xC9)); + } + + #[test] + fn encode_char_unmapped() { + assert_eq!(CP437.encode_char('🦀'), None); + } + + #[test] + fn decode_byte_complete() { + // CP437 is a complete codepage: decode_byte returns `char`. + assert_eq!(CP437.decode_byte(b't'), 't'); + assert_eq!(CP437.decode_byte(0xDB), '█'); + assert_eq!(CP437.decode_byte(0xC9), '╔'); + } + + #[test] + fn decode_byte_incomplete() { + // CP864 is an incomplete codepage: decode_byte returns `Option`, + // and has a nonstandard ASCII mapping at 0x25 -> '٪'. + assert_eq!(CP864.decode_byte(0x25), Some('٪')); + assert_eq!(CP864.decode_byte(b't'), Some('t')); + } +} + +#[cfg(all(test, feature = "alloc"))] mod tests { use crate::code_pages::{CP1253, CP1255, CP1257, CP857, CP864, CP869, CP874}; use crate::CodePage; From 62109dd9c59feb124659a805ec6cc2e740e431e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1=20de=20Mello?= Date: Sun, 14 Jun 2026 14:44:54 +0100 Subject: [PATCH 2/2] chore: bump to 2.0.0 for the no_std default-features change Per maintainer review on #30: the new no-allocator default-features tier is a breaking change for default-features = false users, so bump the major version. The default build is unaffected. --- CHANGELOG.md | 4 ++-- Cargo.toml | 2 +- README.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b8499..ead974c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.5.0] - 2026-06-13 +## [2.0.0] - 2026-06-13 - Add allocation-free `encode_char` / `decode_byte` primitives, available without an allocator (e.g. for `no_std` embedded targets). - Add an `alloc` feature gating the `Cow`-returning `encode`/`decode` API. `std` now implies `alloc`, so the default build is unchanged. -- **Behavior change:** `default-features = false` previously implied an allocator and kept the full API. It now selects the no-allocator tier; add `features = ["alloc"]` to restore the `no_std` + allocator behavior introduced in 1.4.0. +- **Breaking change** (only for the `default-features = false` case): `default-features = false` previously implied an allocator and kept the full API. It now selects the no-allocator tier; add `features = ["alloc"]` to restore the `no_std` + allocator behavior introduced in 1.4.0. The default build is unaffected. ## [1.4.0] - 2026-06-09 - Add `no_std` support. Disable the default `std` feature (`default-features = false`) to build without the standard library; the crate still requires an allocator. The only difference is that the `std::error::Error` impls for `EncodeError`/`DecodeError` are omitted. No changes for existing users. diff --git a/Cargo.toml b/Cargo.toml index 9ce30b8..83792a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yore" -version = "1.5.0" +version = "2.0.0" authors = ["Andreas Liljeqvist "] edition = "2021" categories = ["encoding"] diff --git a/README.md b/README.md index 23744df..8d2b403 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Rust library for decoding and encoding character sets based on OEM code pages. -[![yore at crates.io](https://img.shields.io/badge/crates.io-1.5.0-blue)](https://crates.io/crates/yore) +[![yore at crates.io](https://img.shields.io/badge/crates.io-2.0.0-blue)](https://crates.io/crates/yore) [![yore at docs.rs](https://docs.rs/yore/badge.svg)](https://docs.rs/yore) # Features @@ -20,7 +20,7 @@ Add `yore` to your `Cargo.toml` file. ```toml [dependencies] -yore = "1.5.0" +yore = "2.0.0" ``` ## `no_std` @@ -41,10 +41,10 @@ requires the `alloc` feature. Without it, only the allocation-free ```toml [dependencies] # no_std with an allocator: keep the full Cow-returning API -yore = { version = "1.5.0", default-features = false, features = ["alloc"] } +yore = { version = "2.0.0", default-features = false, features = ["alloc"] } # no_std without an allocator: char primitives only -yore = { version = "1.5.0", default-features = false } +yore = { version = "2.0.0", default-features = false } ``` ```rust