///|
/// The first sort-key level that decides a comparison.
pub(all) enum DifferenceLevel {
PrimaryLevel
SecondaryLevel
TertiaryLevel
QuaternaryLevel
IdenticalLevel
} derive(Debug, Eq, ToJson)
///|
pub fn DifferenceLevel::to_string(self : DifferenceLevel) -> String {
match self {
PrimaryLevel => "primary"
SecondaryLevel => "secondary"
TertiaryLevel => "tertiary"
QuaternaryLevel => "quaternary"
IdenticalLevel => "identical"
}
}
///|
/// Location and weights of the first decisive sort-key difference.
pub(all) struct ComparisonDifference {
level : DifferenceLevel
index : Int
left_weight : Int
right_weight : Int
} derive(Debug, Eq, ToJson)
///|
pub fn ComparisonDifference::level(
self : ComparisonDifference,
) -> DifferenceLevel {
self.level
}
///|
pub fn ComparisonDifference::index(self : ComparisonDifference) -> Int {
self.index
}
///|
pub fn ComparisonDifference::left_weight(self : ComparisonDifference) -> Int {
self.left_weight
}
///|
pub fn ComparisonDifference::right_weight(self : ComparisonDifference) -> Int {
self.right_weight
}
///|
/// A comparison result with both keys and the first decisive difference.
pub(all) struct ComparisonReport {
ordering : Ordering
difference : ComparisonDifference?
left_key : SortKey
right_key : SortKey
} derive(Debug, Eq, ToJson)
///|
pub fn ComparisonReport::ordering(self : ComparisonReport) -> Ordering {
self.ordering
}
///|
pub fn ComparisonReport::difference(
self : ComparisonReport,
) -> ComparisonDifference? {
self.difference
}
///|
pub fn ComparisonReport::left_key(self : ComparisonReport) -> SortKey {
self.left_key
}
///|
pub fn ComparisonReport::right_key(self : ComparisonReport) -> SortKey {
self.right_key
}
///|
fn first_weight_difference(
level : DifferenceLevel,
left : Array[Int],
right : Array[Int],
) -> ComparisonDifference? {
let shared = if left.length() < right.length() {
left.length()
} else {
right.length()
}
for index = 0; index < shared; index = index + 1 {
if left[index] != right[index] {
return Some({
level,
index,
left_weight: left[index],
right_weight: right[index],
})
}
}
if left.length() != right.length() {
Some({
level,
index: shared,
left_weight: if shared < left.length() {
left[shared]
} else {
0
},
right_weight: if shared < right.length() {
right[shared]
} else {
0
},
})
} else {
None
}
}
///|
fn decisive_difference(
strength : Strength,
left : SortKey,
right : SortKey,
) -> ComparisonDifference? {
match first_weight_difference(PrimaryLevel, left.primary, right.primary) {
Some(value) => return Some(value)
None => ()
}
if strength_rank(strength) < 2 {
return None
}
match
first_weight_difference(SecondaryLevel, left.secondary, right.secondary) {
Some(value) => return Some(value)
None => ()
}
if strength_rank(strength) < 3 {
return None
}
match first_weight_difference(TertiaryLevel, left.tertiary, right.tertiary) {
Some(value) => return Some(value)
None => ()
}
if strength_rank(strength) < 4 {
return None
}
match
first_weight_difference(QuaternaryLevel, left.quaternary, right.quaternary) {
Some(value) => return Some(value)
None => ()
}
if strength_rank(strength) < 5 {
return None
}
first_weight_difference(IdenticalLevel, left.identical, right.identical)
}
///|
/// Compare strings and retain enough structured evidence for diagnostics.
pub fn Collator::compare_detailed(
self : Collator,
left : String,
right : String,
) -> ComparisonReport {
let left_key = self.sort_key(left)
let right_key = self.sort_key(right)
{
ordering: self.compare_keys(left_key, right_key),
difference: decisive_difference(self.strength(), left_key, right_key),
left_key,
right_key,
}
}
///|
/// Return true when two strings are equal at the configured strength.
pub fn Collator::equivalent(
self : Collator,
left : String,
right : String,
) -> Bool {
self.compare(left, right) == Equal
}
///|
fn append_level(target : Array[Int], source : Array[Int]) -> Unit {
for weight in source {
target.push(weight)
}
target.push(0)
}
///|
/// Flatten a structured key through `strength`, separating levels with zero.
/// All emitted UCA weights are non-zero, so separators remain unambiguous.
pub fn SortKey::flatten(self : SortKey, strength : Strength) -> Array[Int] {
let result : Array[Int] = []
append_level(result, self.primary)
if strength_rank(strength) >= 2 {
append_level(result, self.secondary)
}
if strength_rank(strength) >= 3 {
append_level(result, self.tertiary)
}
if strength_rank(strength) >= 4 {
append_level(result, self.quaternary)
}
if strength_rank(strength) >= 5 {
append_level(result, self.identical)
}
result
}
///|
fn hex_digit(value : Int) -> Char {
if value < 10 {
(0x30 + value).to_char().unwrap()
} else {
(0x41 + value - 10).to_char().unwrap()
}
}
///|
fn weight_hex(value : Int) -> String {
if value == 0 {
return "0000"
}
let digits : Array[Char] = []
let mut remaining = value
while remaining > 0 {
digits.push(hex_digit(remaining % 16))
remaining = remaining / 16
}
while digits.length() < 4 {
digits.push('0')
}
let ordered : Array[Char] = []
for index = digits.length() - 1; index >= 0; index = index - 1 {
ordered.push(digits[index])
}
String::from_array(ordered)
}
///|
/// Format a flattened key as uppercase hexadecimal words.
pub fn SortKey::to_hex(self : SortKey, strength : Strength) -> String {
let out = StringBuilder()
for index, weight in self.flatten(strength) {
if index > 0 {
out.write_char(' ')
}
out.write_string(weight_hex(weight))
}
out.to_string()
}