Shared Rust kernels

catalogue/comparison/collation

All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.

compare · 23 lines · calc-func/src/catalogue/comparison/collation.rs on GitHub
/// The order of two texts as the comparison operators give it, or [`Beyond`] naming the first of
/// them whose weights are not built here.
pub(super) fn compare(a: &[u16], b: &[u16]) -> Result<Ordering, Beyond> {
    // NUL is outside the measured comparison domain, including an ASCII
    // prefix that the first pass would otherwise skip or decide by length.
    if let Some(operand) = [a, b].iter().position(|text| text.contains(&0)) {
        return Err(Beyond { operand });
    }
    let fold = |code: u16| {
        if (0x0061..=FIRST_PASS_LAST).contains(&code) {
            code - 0x20
        } else {
            code
        }
    };
    for (at, (&x, &y)) in a.iter().zip(b).enumerate() {
        if x > FIRST_PASS_LAST || y > FIRST_PASS_LAST || fold(x) != fold(y) {
            // The second pass reads the texts from there on, not from their starts.
            return weighed(&a[at..], &b[at..]);
        }
    }
    Ok(a.len().cmp(&b.len()))
}

Used by 38 functions.

weighed · 32 lines · calc-func/src/catalogue/comparison/collation.rs on GitHub
/// The second pass: the two texts' primary weights, then their diacritics as `CompareStringEx`
/// walks them ([`diacritic_order`]), then the rest of their sort keys (case, kana, specials).
fn weighed(a: &[u16], b: &[u16]) -> Result<Ordering, Beyond> {
    if let Some((length, left, right)) = shared_jamo_tails(a, b) {
        // Keep one common default entry for marks at the suffix boundary.
        // Calling this pass directly preserves NLS's treatment of ignorables;
        // Excel's ASCII length shortcut has already finished on the originals.
        let prefix = |tail: &[u16]| a[..length].iter().copied()
            .chain(std::iter::once(0x0061)).chain(tail.iter().copied()).collect::<Vec<_>>();
        return weighed(&prefix(left), &prefix(right));
    }
    let left = parts(a);
    let right = parts(b);
    if left.is_err() || right.is_err() {
        return leading_order(&left, &right).or_else(|| {
            match (left.is_ok(), right.is_ok()) {
                (true, false) if measured_iteration_extension(a, b) => Some(Ordering::Less),
                (false, true) if measured_iteration_extension(b, a) => Some(Ordering::Greater),
                _ => None,
            }
        }).ok_or(Beyond {
            operand: usize::from(left.is_ok()),
        });
    }
    let left = left.map_err(|_| Beyond { operand: 0 })?;
    let right = right.map_err(|_| Beyond { operand: 1 })?;
    Ok(left
        .primary
        .cmp(&right.primary)
        .then_with(|| diacritic_order(a, &left, b, &right))
        .then_with(|| left.rest.cmp(&right.rest)))
}

Used by 38 functions.

measured_iteration_extension · 15 lines · calc-func/src/catalogue/comparison/collation.rs on GitHub
/// Fn3 records a strict order against the exact bare prefix for these three marks immediately
/// after these three specials. This does not assign a primary weight, or compare different
/// prefixes with equal primaries. The two-unit suffix follows by transitivity: a completed
/// prefix precedes that prefix plus its known special, which precedes the measured mark.
/// A combining unit, another mark/special, or a tail beyond this suffix remains unmodelled.
fn measured_iteration_extension(shorter: &[u16], longer: &[u16]) -> bool {
    let Some(suffix) = longer.strip_prefix(shorter) else { return false; };
    let special = |unit: u16| matches!(unit, 0x002D | 0x0027 | 0x2010);
    let mark = |unit: u16| matches!(unit, 0x309D | 0x3005 | 0x30FC);
    match suffix {
        &[iteration] => mark(iteration) && shorter.last().is_some_and(|&unit| special(unit)),
        &[punctuation, iteration] => special(punctuation) && mark(iteration),
        _ => false,
    }
}

Used by 38 functions.