///|
pub(all) enum TokenKind {
PinyinToken
LiteralToken
} derive(Eq, Debug)
///|
pub(all) enum DecisionSource {
PhraseLexicon
UnicodeReading
UnknownPreserved
} derive(Eq, Debug)
///|
/// One auditable output token tied to a scalar span in the source.
pub struct ConvertedToken {
original_value : String
rendered_value : String
token_kind : TokenKind
decision_value : DecisionSource
source_span : SourceSpan
candidate_values : Array[String]
} derive(Eq, Debug)
///|
/// Structured conversion result with output and decision evidence.
pub struct ConversionResult {
source_value : String
rendered_value : String
token_values : Array[ConvertedToken]
diagnostic_values : Array[Diagnostic]
} derive(Eq, Debug)
///|
fn copy_tokens(values : Array[ConvertedToken]) -> Array[ConvertedToken] {
let output : Array[ConvertedToken] = []
for value in values {
output.push(value)
}
output
}
///|
fn copy_diagnostics(values : Array[Diagnostic]) -> Array[Diagnostic] {
let output : Array[Diagnostic] = []
for value in values {
output.push(value)
}
output
}
///|
fn render_raw_reading(
raw : String,
options : ConvertOptions,
) -> Result[String, PinyinError] {
match parse_syllable(raw) {
Ok(value) => Ok(value.format(options.output_style, options.output_case))
Err(_) => Err(InvalidGeneratedData("invalid_unihan_reading:" + raw))
}
}
///|
fn render_reading_set(
set : ReadingSet,
options : ConvertOptions,
) -> Result[String, PinyinError] {
let readings = set.readings()
if readings.length() == 0 {
return Err(UnknownCharacter(set.code_point()))
}
if readings.length() > 1 && options.ambiguity_behavior == RejectAmbiguous {
return Err(AmbiguousCharacter(set.code_point(), readings.length()))
}
let limit = if options.ambiguity_behavior == PreserveAllReadings {
readings.length()
} else {
1
}
let mut output = ""
for index = 0; index < limit; index = index + 1 {
if index > 0 {
output = output + "/"
}
let rendered_reading = match render_raw_reading(readings[index], options) {
Err(error) => return Err(error)
Ok(value) => value
}
output = output + rendered_reading
}
Ok(output)
}
///|
fn append_rendered(
current : String,
rendered : String,
kind : TokenKind,
previous : TokenKind?,
separator : String,
) -> String {
if previous == Some(PinyinToken) && kind == PinyinToken {
current + separator + rendered
} else {
current + rendered
}
}
///|
/// Converts mixed text using phrase overrides before Unicode character lookup.
pub fn convert_text(
source : String,
lexicon : Lexicon,
options : ConvertOptions,
) -> Result[ConversionResult, PinyinError] {
let codes = text_code_points(source)
let tokens : Array[ConvertedToken] = []
let diagnostics : Array[Diagnostic] = []
let mut rendered = ""
let mut previous_kind : TokenKind? = None
let mut cursor = 0
while cursor < codes.length() {
match lexicon.longest_match(codes, cursor) {
Some(found) => {
let phrase_readings = found.readings()
for offset = 0; offset < found.length(); offset = offset + 1 {
let syllable = phrase_readings[offset]
let output = syllable.format(
options.output_style,
options.output_case,
)
let span = source_span(cursor + offset, cursor + offset + 1).unwrap()
let token = {
original_value: code_points_text([codes[cursor + offset]]),
rendered_value: output,
token_kind: PinyinToken,
decision_value: PhraseLexicon,
source_span: span,
candidate_values: [syllable.format(ToneMarks, Lowercase)],
}
rendered = append_rendered(
rendered,
output,
PinyinToken,
previous_kind,
options.token_separator,
)
previous_kind = Some(PinyinToken)
tokens.push(token)
}
cursor = cursor + found.length()
}
None => {
let code = codes[cursor]
match lookup_readings(code) {
Some(set) => {
let candidates = set.readings()
let output = match render_reading_set(set, options) {
Err(error) => return Err(error)
Ok(value) => value
}
let span = source_span(cursor, cursor + 1).unwrap()
if candidates.length() > 1 {
diagnostics.push(
diagnostic("ambiguous", "multiple Unicode readings", Some(span)),
)
}
tokens.push({
original_value: code_points_text([code]),
rendered_value: output,
token_kind: PinyinToken,
decision_value: UnicodeReading,
source_span: span,
candidate_values: candidates,
})
rendered = append_rendered(
rendered,
output,
PinyinToken,
previous_kind,
options.token_separator,
)
previous_kind = Some(PinyinToken)
}
None =>
match options.unknown_behavior {
RejectUnknown => return Err(UnknownCharacter(code))
DropUnknown =>
diagnostics.push(
diagnostic(
"unknown_dropped",
"unknown character was dropped",
Some(source_span(cursor, cursor + 1).unwrap()),
),
)
PreserveUnknown => {
let original = code_points_text([code])
let span = source_span(cursor, cursor + 1).unwrap()
tokens.push({
original_value: original,
rendered_value: original,
token_kind: LiteralToken,
decision_value: UnknownPreserved,
source_span: span,
candidate_values: [],
})
rendered = append_rendered(
rendered,
original,
LiteralToken,
previous_kind,
options.token_separator,
)
previous_kind = Some(LiteralToken)
}
}
}
cursor = cursor + 1
}
}
}
Ok({
source_value: source,
rendered_value: rendered,
token_values: tokens,
diagnostic_values: diagnostics,
})
}
///|
pub fn ConvertedToken::original(self : ConvertedToken) -> String {
self.original_value
}
///|
pub fn ConvertedToken::rendered(self : ConvertedToken) -> String {
self.rendered_value
}
///|
pub fn ConvertedToken::kind(self : ConvertedToken) -> TokenKind {
self.token_kind
}
///|
pub fn ConvertedToken::decision_source(self : ConvertedToken) -> DecisionSource {
self.decision_value
}
///|
pub fn ConvertedToken::span(self : ConvertedToken) -> SourceSpan {
self.source_span
}
///|
pub fn ConvertedToken::candidates(self : ConvertedToken) -> Array[String] {
self.candidate_values.copy()
}
///|
pub fn ConversionResult::source(self : ConversionResult) -> String {
self.source_value
}
///|
pub fn ConversionResult::rendered(self : ConversionResult) -> String {
self.rendered_value
}
///|
pub fn ConversionResult::tokens(
self : ConversionResult,
) -> Array[ConvertedToken] {
copy_tokens(self.token_values)
}
///|
pub fn ConversionResult::diagnostics(
self : ConversionResult,
) -> Array[Diagnostic] {
copy_diagnostics(self.diagnostic_values)
}