///|
struct LexiconEntry {
phrase_value : String
code_values : Array[Int]
reading_values : Array[Syllable]
} derive(Eq, Debug)
///|
struct LexiconNode {
children : Map[Int, Int]
entries : Array[Int]
}
///|
/// Mutable construction surface with validation on every insertion.
pub struct LexiconBuilder {
values : Array[LexiconEntry]
}
///|
/// Immutable phrase resolver backed by a flattened Trie.
pub struct Lexicon {
nodes : Array[LexiconNode]
values : Array[LexiconEntry]
}
///|
/// Evidence returned for one longest phrase match.
pub struct LexiconMatch {
phrase_value : String
consumed : Int
reading_values : Array[Syllable]
} derive(Eq, Debug)
///|
fn copy_syllables(values : Array[Syllable]) -> Array[Syllable] {
let output : Array[Syllable] = []
for value in values {
output.push(value)
}
output
}
///|
pub fn LexiconBuilder::new() -> LexiconBuilder {
{ values: [] }
}
///|
/// Adds one phrase after validating scalar and reading counts.
pub fn LexiconBuilder::add(
self : LexiconBuilder,
phrase : String,
readings : Array[String],
) -> Result[Unit, PinyinError] {
let codes = text_code_points(phrase)
if codes.length() == 0 {
return Err(InvalidLexiconEntry(phrase, "empty_phrase"))
}
if codes.length() != readings.length() {
return Err(InvalidLexiconEntry(phrase, "reading_count_mismatch"))
}
for existing in self.values {
if existing.phrase_value == phrase {
return Err(ConflictingLexiconEntry(phrase))
}
}
let parsed : Array[Syllable] = []
for reading in readings {
match parse_syllable(reading) {
Err(error) => return Err(error)
Ok(value) => parsed.push(value)
}
}
self.values.push({
phrase_value: phrase,
code_values: codes,
reading_values: parsed,
})
Ok(())
}
///|
/// Freezes entries into deterministic Trie nodes.
pub fn LexiconBuilder::build(self : LexiconBuilder) -> Lexicon {
let entries : Array[LexiconEntry] = []
for value in self.values {
entries.push({
phrase_value: value.phrase_value,
code_values: value.code_values.copy(),
reading_values: copy_syllables(value.reading_values),
})
}
let nodes : Array[LexiconNode] = [{ children: Map([]), entries: [] }]
for entry_index = 0
entry_index < entries.length()
entry_index = entry_index + 1 {
let mut node_index = 0
for code in entries[entry_index].code_values {
match nodes[node_index].children.get(code) {
Some(next) => node_index = next
None => {
let next = nodes.length()
nodes.push({ children: Map([]), entries: [] })
nodes[node_index].children.set(code, next)
node_index = next
}
}
}
nodes[node_index].entries.push(entry_index)
}
{ nodes, values: entries }
}
///|
/// Returns the longest phrase beginning at a scalar offset.
pub fn Lexicon::longest_match(
self : Lexicon,
source : Array[Int],
start : Int,
) -> LexiconMatch? {
if start < 0 || start >= source.length() {
return None
}
let mut node_index = 0
let mut cursor = start
let mut best_entry = -1
let mut best_length = 0
while cursor < source.length() {
match self.nodes[node_index].children.get(source[cursor]) {
None => break
Some(next) => {
node_index = next
cursor = cursor + 1
if self.nodes[node_index].entries.length() > 0 {
best_entry = self.nodes[node_index].entries[0]
best_length = cursor - start
}
}
}
}
if best_entry < 0 {
None
} else {
let entry = self.values[best_entry]
Some({
phrase_value: entry.phrase_value,
consumed: best_length,
reading_values: copy_syllables(entry.reading_values),
})
}
}
///|
pub fn LexiconMatch::phrase(self : LexiconMatch) -> String {
self.phrase_value
}
///|
pub fn LexiconMatch::length(self : LexiconMatch) -> Int {
self.consumed
}
///|
pub fn LexiconMatch::readings(self : LexiconMatch) -> Array[Syllable] {
copy_syllables(self.reading_values)
}