sim-text 0.1.0

Neutral exact text values and explicit Unicode projections for SIM.
Documentation
use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

#[test]
fn length_index_slice_and_code_unit_iteration_are_utf16() {
    let string = CodeUnitString::from_scalar("A\u{1f600}B");
    assert_eq!(string.len(), 4);
    assert_eq!(string.code_unit_at(CodeUnitOffset::new(1)), Some(0xd83d));
    assert_eq!(string.as_code_units(), [0x0041, 0xd83d, 0xde00, 0x0042]);
    assert_eq!(
        string
            .slice(CodeUnitRange::new(
                CodeUnitOffset::new(1),
                CodeUnitOffset::new(3)
            ))
            .as_code_units(),
        [0xd83d, 0xde00]
    );
    assert_eq!(string.code_units().count(), 4);
}

#[test]
fn scalar_conversion_and_paired_iteration_are_exact() {
    let string = CodeUnitString::from_code_units(vec![0xd83d, 0xde00]);
    assert_eq!(string.to_scalar().unwrap(), "\u{1f600}");
    assert_eq!(
        string.iter_code_points().next().unwrap().as_code_units(),
        [0xd83d, 0xde00]
    );
}

#[test]
fn lone_high_surrogate_is_preserved_and_rejected_by_scalar_face() {
    let string = CodeUnitString::from_code_units(vec![0xd800]);
    assert_eq!(string.code_unit_at(CodeUnitOffset::new(0)), Some(0xd800));
    assert_eq!(
        string.iter_code_points().next().unwrap().as_code_units(),
        [0xd800]
    );
    assert_eq!(
        string.to_scalar(),
        Err(CodeUnitStringError::LoneSurrogate(InvalidSurrogate {
            offset: CodeUnitOffset::new(0),
            unit: 0xd800
        }))
    );
}

#[test]
fn lone_low_surrogate_is_preserved_and_rejected_by_scalar_face() {
    let string = CodeUnitString::from_code_units(vec![0xdc00]);
    assert_eq!(
        string.slice(CodeUnitRange::new(
            CodeUnitOffset::new(0),
            CodeUnitOffset::new(1)
        )),
        string
    );
    assert_eq!(
        string.to_scalar(),
        Err(CodeUnitStringError::LoneSurrogate(InvalidSurrogate {
            offset: CodeUnitOffset::new(0),
            unit: 0xdc00
        }))
    );
}

#[test]
fn pair_iteration_preserves_lone_units_and_nul() {
    let string =
        CodeUnitString::from_code_units(vec![0xd800, 0x0061, 0xd83d, 0xde00, 0xdc00, 0x0000]);
    let chunks: Vec<Vec<u16>> = string
        .iter_code_points()
        .map(|chunk| chunk.code_units().collect())
        .collect();
    assert_eq!(
        chunks,
        vec![
            vec![0xd800],
            vec![0x0061],
            vec![0xd83d, 0xde00],
            vec![0xdc00],
            vec![0x0000]
        ]
    );
}

#[test]
fn offset_conversion_is_bounded_and_never_splits_a_pair() {
    let string = CodeUnitString::from_scalar("A\u{1f600}B");
    assert_eq!(
        string.code_unit_offset(ScalarOffset::new(2)),
        Ok(CodeUnitOffset::new(3))
    );
    assert_eq!(
        string.scalar_offset(CodeUnitOffset::new(3)),
        Ok(ScalarOffset::new(2))
    );
    assert_eq!(
        string.scalar_offset(CodeUnitOffset::new(2)),
        Err(OffsetConversionError::NotScalarBoundary(
            CodeUnitOffset::new(2)
        ))
    );
    assert_eq!(
        string.code_unit_offset(ScalarOffset::new(4)),
        Err(OffsetConversionError::ScalarOutOfBounds {
            offset: ScalarOffset::new(4),
            len: 3
        })
    );
}

#[test]
fn scalar_text_round_trips_for_generated_unicode_sequences() {
    let alphabet = ['\0', 'a', '\u{df}', '\u{4e2d}', '\u{1f600}', '\u{10ffff}'];
    for seed in 0usize..512 {
        let text: String = (0..seed % 17)
            .map(|at| alphabet[(seed.wrapping_mul(17) ^ at.wrapping_mul(31)) % alphabet.len()])
            .collect();
        assert_eq!(
            CodeUnitString::from_scalar(&text).to_scalar().unwrap(),
            text
        );
    }
}

#[test]
fn arbitrary_raw_units_are_preserved_by_value_equality_and_hash() {
    let mut state = 0x5eed_cafe_dead_beefu64;
    for len in 0..128 {
        let units: Vec<u16> = (0..len)
            .map(|_| {
                state ^= state << 13;
                state ^= state >> 7;
                state ^= state << 17;
                state as u16
            })
            .collect();
        let left = CodeUnitString::from_code_units(units.clone());
        let right = CodeUnitString::from_code_units(units.clone());
        assert_eq!(left.as_code_units(), units);
        assert_eq!(left, right);
        let mut left_hash = DefaultHasher::new();
        let mut right_hash = DefaultHasher::new();
        left.hash(&mut left_hash);
        right.hash(&mut right_hash);
        assert_eq!(left_hash.finish(), right_hash.finish());
    }
}