///|
/// Readings attached to one Unicode scalar value.
pub struct ReadingSet {
code_value : Int
readings_value : Array[String]
} derive(Eq, Debug)
///|
/// Counts and boundaries proven by a full generated-data validation pass.
pub struct DatasetStats {
code_point_count : Int
reading_count : Int
ambiguous_count : Int
maximum_reading_count : Int
first_code_value : Int
last_code_value : Int
} derive(Eq, Debug)
///|
fn reading_copy(values : Array[String]) -> Array[String] {
let output : Array[String] = []
for value in values {
output.push(value)
}
output
}
///|
fn split_reading_values(value : String) -> Array[String] {
let output : Array[String] = []
let mut start = 0
for index = 0; index < value.length(); index = index + 1 {
if value[index].to_int() == 44 {
if index > start {
output.push(syllable_ascii_slice(value, start, index))
}
start = index + 1
}
}
if start < value.length() {
output.push(syllable_ascii_slice(value, start, value.length()))
}
output
}
///|
fn readings_are_unique(values : Array[String]) -> Bool {
for left = 0; left < values.length(); left = left + 1 {
for right = left + 1; right < values.length(); right = right + 1 {
if values[left] == values[right] {
return false
}
}
}
true
}
///|
/// Validates every bundled code point and reading, then returns dataset facts.
pub fn validate_unihan_dataset() -> Result[DatasetStats, PinyinError] {
if unihan_code_points.length() != unihan_readings.length() {
return Err(InvalidGeneratedData("code_reading_length_mismatch"))
}
if unihan_code_points.length() == 0 {
return Err(InvalidGeneratedData("empty_dataset"))
}
let mut reading_count = 0
let mut ambiguous_count = 0
let mut maximum_readings = 0
for index = 0; index < unihan_code_points.length(); index = index + 1 {
let code = unihan_code_points[index]
if code < 0 || code > 0x10FFFF || (code >= 0xD800 && code <= 0xDFFF) {
return Err(InvalidGeneratedData("invalid_code_point:" + code.to_string()))
}
if index > 0 && unihan_code_points[index - 1] >= code {
return Err(InvalidGeneratedData("code_points_not_strictly_sorted"))
}
let values = split_reading_values(unihan_readings[index])
if values.length() == 0 {
return Err(InvalidGeneratedData("empty_reading_list:" + code.to_string()))
}
if !readings_are_unique(values) {
return Err(InvalidGeneratedData("duplicate_reading:" + code.to_string()))
}
for value in values {
match parse_syllable(value) {
Err(_) =>
return Err(
InvalidGeneratedData(
"invalid_reading:" + code.to_string() + ":" + value,
),
)
Ok(_) => ()
}
}
reading_count = reading_count + values.length()
if values.length() > 1 {
ambiguous_count = ambiguous_count + 1
}
if values.length() > maximum_readings {
maximum_readings = values.length()
}
}
Ok({
code_point_count: unihan_code_points.length(),
reading_count,
ambiguous_count,
maximum_reading_count: maximum_readings,
first_code_value: unihan_code_points[0],
last_code_value: unihan_code_points[unihan_code_points.length() - 1],
})
}
///|
/// Finds a sorted code point in the generated Unicode table.
pub fn lookup_readings(code_point : Int) -> ReadingSet? {
if code_point < 0 {
return None
}
let mut low = 0
let mut high = unihan_code_points.length() - 1
while low <= high {
let middle = low + (high - low) / 2
let candidate = unihan_code_points[middle]
if candidate == code_point {
return Some({
code_value: code_point,
readings_value: split_reading_values(unihan_readings[middle]),
})
}
if candidate < code_point {
low = middle + 1
} else {
high = middle - 1
}
}
None
}
///|
pub fn ReadingSet::code_point(self : ReadingSet) -> Int {
self.code_value
}
///|
pub fn ReadingSet::readings(self : ReadingSet) -> Array[String] {
reading_copy(self.readings_value)
}
///|
pub fn ReadingSet::is_ambiguous(self : ReadingSet) -> Bool {
self.readings_value.length() > 1
}
///|
pub fn DatasetStats::code_points(self : DatasetStats) -> Int {
self.code_point_count
}
///|
pub fn DatasetStats::readings(self : DatasetStats) -> Int {
self.reading_count
}
///|
pub fn DatasetStats::ambiguous_code_points(self : DatasetStats) -> Int {
self.ambiguous_count
}
///|
pub fn DatasetStats::maximum_readings_per_code_point(
self : DatasetStats,
) -> Int {
self.maximum_reading_count
}
///|
pub fn DatasetStats::first_code_point(self : DatasetStats) -> Int {
self.first_code_value
}
///|
pub fn DatasetStats::last_code_point(self : DatasetStats) -> Int {
self.last_code_value
}
///|
/// Selects a reading while making the ambiguity policy observable.
pub fn select_reading(
code_point : Int,
policy : AmbiguityPolicy,
) -> Result[String, PinyinError] {
let result = match lookup_readings(code_point) {
None => return Err(UnknownCharacter(code_point))
Some(value) => value
}
if result.readings_value.length() == 0 {
return Err(UnknownCharacter(code_point))
}
if result.readings_value.length() > 1 {
match policy {
RejectAmbiguous =>
return Err(
AmbiguousCharacter(code_point, result.readings_value.length()),
)
PreserveAllReadings => {
let mut joined = ""
for index = 0; index < result.readings_value.length(); index = index + 1 {
if index > 0 {
joined = joined + "|"
}
joined = joined + result.readings_value[index]
}
return Ok(joined)
}
SelectFirstReading => ()
}
}
Ok(result.readings_value[0])
}