///|
priv struct NormalizedRomanUnit {
span : SourceSpan
source_text : String
normalized_text : String
}
///|
priv struct NormalizedRomanInput {
original : String
normalized : String
units : Array[NormalizedRomanUnit]
content_span : SourceSpan
used_unicode_compatibility : Bool
trimmed_outer_whitespace : Bool
}
///|
fn roman_character_string(character : Char) -> String {
String::from_array([character])
}
///|
fn normalized_content_bounds(
chars : Array[Char],
trim : Bool,
) -> (Int, Int, Bool) {
if !trim {
return (0, chars.length(), false)
}
let mut start = 0
while start < chars.length() && is_outer_whitespace(chars[start]) {
start = start + 1
}
let mut end = chars.length()
while end > start && is_outer_whitespace(chars[end - 1]) {
end = end - 1
}
(start, end, start > 0 || end < chars.length())
}
///|
fn normalize_ascii_roman_unit(
character : Char,
index : Int,
config : ParseConfig,
) -> Result[NormalizedRomanUnit, RomanReportError] {
let span : SourceSpan = { start: index, end: index + 1 }
if is_lowercase_roman_letter(character) && !config.accept_lowercase {
return Err(LowercaseRomanNotAllowed(span, character))
}
Ok({
span,
source_text: roman_character_string(character),
normalized_text: roman_character_string(character.to_ascii_uppercase()),
})
}
///|
fn normalize_report_unit(
character : Char,
index : Int,
config : ParseConfig,
) -> Result[(NormalizedRomanUnit, Bool), RomanReportError] {
if is_ascii_roman_letter(character) {
return match normalize_ascii_roman_unit(character, index, config) {
Ok(unit) => Ok((unit, false))
Err(error) => Err(error)
}
}
if config.mode == ParenthesizedThousands &&
(character == '(' || character == ')') {
let text = roman_character_string(character)
return Ok(
(
{
span: { start: index, end: index + 1 },
source_text: text,
normalized_text: text,
},
false,
),
)
}
if config.accept_unicode {
match unicode_roman_expansion(character) {
Some(expansion) =>
return Ok(
(
{
span: { start: index, end: index + 1 },
source_text: roman_character_string(character),
normalized_text: expansion,
},
true,
),
)
None => ()
}
}
Err(UnsupportedRomanCharacter({ start: index, end: index + 1 }, character))
}
///|
fn normalize_for_report(
input : String,
config : ParseConfig,
) -> Result[NormalizedRomanInput, RomanReportError] {
let chars = input.to_array()
let bounds = normalized_content_bounds(chars, config.trim_outer_whitespace)
let start = bounds.0
let end = bounds.1
if start >= end {
return Err(EmptyRomanInput)
}
let units : Array[NormalizedRomanUnit] = []
let mut normalized = ""
let mut used_unicode = false
for index = start; index < end; index = index + 1 {
let result = match normalize_report_unit(chars[index], index, config) {
Ok(value) => value
Err(error) => return Err(error)
}
units.push(result.0)
normalized = normalized + result.0.normalized_text
if result.1 {
used_unicode = true
}
}
Ok({
original: input,
normalized,
units,
content_span: { start, end },
used_unicode_compatibility: used_unicode,
trimmed_outer_whitespace: bounds.2,
})
}
///|
/// Normalize configured Roman input to deterministic uppercase ASCII text.
pub fn normalize_roman_input(
input : String,
config : ParseConfig,
) -> Result[String, RomanReportError] {
match normalize_for_report(input, config) {
Ok(value) => Ok(value.normalized)
Err(error) => Err(error)
}
}