srcdmp 0.1.0-alpha.1

Amazing code snapshot tool and library
Documentation
include!(concat!(env!("OUT_DIR"), "/tier2_table.rs"));

use crate::raw::error::SdmpError;
use crate::raw::table::{ESCAPE, TIER1_DECODE, build_tier1_encode};

use std::collections::HashMap;
use std::sync::LazyLock;

/// The lazily-initialized tier-2 decode table (generated at build time).
static TIER2_DECODE: LazyLock<Vec<Option<char>>> = LazyLock::new(build_tier2_decode);
/// The lazily-initialized tier-2 encode map (generated at build time).
static TIER2_ENCODE: LazyLock<HashMap<char, u16>> = LazyLock::new(build_tier2_encode);

/// Decode SDMP-7 encoded bytes into a UTF-8 string.
///
/// SDMP-7 uses a three-tier encoding scheme:
///
/// * **Tier 1** (single byte `0x00–0x7E`): Characters present in the
///   [`TIER1_DECODE`] table are represented as a single byte.
/// * **Escape** (`0x7F`): The following byte is a raw UTF-8 byte.
/// * **Tier 2** (two bytes `0b10xxxxxx 0xxxxxxxxx`): Extended Latin,
///   Greek, mathematical operators, CJK, and other common code points
///   are stored as a 14-bit index into the tier-2 table.
/// * **Tier 3** (three bytes `0b11xxxxxx 0xxxxxxxxx 0xxxxxxxxx`):
///   Any remaining code point is stored as a 22-bit Unicode scalar value.
///
/// # Errors
///
/// Returns [`SdmpError::UnexpectedEof`] if the input is truncated.
/// Returns [`SdmpError::InvalidUtf8`] if a decoded code point is invalid
/// or a tier-1 byte maps to a reserved slot.
///
/// # Examples
///
/// ```rust
/// use srcdmp::raw::codec::sdmp7::{decode, encode};
///
/// let original = "hello, world!";
/// let encoded = encode(original);
/// let decoded = decode(&encoded).unwrap();
/// assert_eq!(decoded, original);
/// ```
pub fn decode(input: &[u8]) -> Result<String, SdmpError> {
    let mut output = String::new();
    let mut i = 0;

    while i < input.len() {
        let byte = input[i];

        if byte == ESCAPE {
            i += 1;
            if i >= input.len() {
                return Err(SdmpError::UnexpectedEof);
            }
            output.push(input[i] as char);
            i += 1;
            continue;
        }

        if byte < 0x80 {
            match TIER1_DECODE[byte as usize] {
                Some(c) => output.push(c),
                None => return Err(SdmpError::InvalidUtf8),
            }
            i += 1;
            continue;
        }

        if byte >> 6 == 0b10 {
            i += 1;
            if i >= input.len() {
                return Err(SdmpError::UnexpectedEof);
            }
            let second = input[i];

            let index = u16::from(byte & 0x3F) << 8 | u16::from(second);

            match TIER2_DECODE.get(index as usize) {
                Some(Some(c)) => output.push(*c),
                _ => return Err(SdmpError::InvalidUtf8),
            }
            i += 1;
            continue;
        }

        if byte >> 6 == 0b11 {
            i += 1;
            if i + 1 >= input.len() {
                return Err(SdmpError::UnexpectedEof);
            }
            let second = input[i];
            let third = input[i + 1];

            let index = u32::from(byte & 0x3F) << 16 | u32::from(second) << 8 | u32::from(third);

            match char::from_u32(index) {
                Some(c) => output.push(c),
                None => return Err(SdmpError::InvalidUtf8),
            }
            i += 2;
            continue;
        }

        return Err(SdmpError::InvalidUtf8);
    }

    Ok(output)
}

/// Encode a UTF-8 string into SDMP-7 bytes.
///
/// Characters present in the tier-1 table are encoded as a single byte.
/// Characters in the tier-2 table are encoded as two bytes.
/// All other code points are encoded as three bytes.
///
/// # Examples
///
/// ```rust
/// use srcdmp::raw::codec::sdmp7::encode;
///
/// let bytes = encode("hello");
/// assert!(!bytes.is_empty());
/// ```
pub fn encode(input: &str) -> Vec<u8> {
    let tier1 = build_tier1_encode();
    let mut output = Vec::new();

    for c in input.chars() {
        if let Some(&index) = tier1.get(&c) {
            output.push(index);
            continue;
        }

        if let Some(&index) = TIER2_ENCODE.get(&c) {
            let high = 0b10000000 | ((index >> 8) & 0x3F) as u8;
            let low = (index & 0xFF) as u8;
            output.push(high);
            output.push(low);
            continue;
        }

        let codepoint = c as u32;
        let high = 0b11000000 | ((codepoint >> 16) & 0x3F) as u8;
        let mid = ((codepoint >> 8) & 0xFF) as u8;
        let low = (codepoint & 0xFF) as u8;
        output.push(high);
        output.push(mid);
        output.push(low);
    }

    output
}