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.