///|
fn normalize_input(input : String) -> Result[Array[Char], ParseError] {
if input.is_empty() {
return Err(EmptyInput)
}
let normalized : Array[Char] = []
let chars = input.to_array()
for index = 0; index < chars.length(); index = index + 1 {
let original = chars[index]
let upper = original.to_ascii_uppercase()
match symbol_value(upper) {
Some(_) => normalized.push(upper)
None => return Err(InvalidCharacter(index, original))
}
}
Ok(normalized)
}
///|
fn validate_repetitions(chars : Array[Char]) -> Result[Unit, ParseError] {
let mut previous = '\u{0}'
let mut run_length = 0
for index = 0; index < chars.length(); index = index + 1 {
let symbol = chars[index]
if symbol == previous {
run_length = run_length + 1
} else {
previous = symbol
run_length = 1
}
let limit = if is_repeatable_symbol(symbol) { 3 } else { 1 }
if run_length > limit {
return Err(InvalidRepetition(index, symbol))
}
}
Ok(())
}
///|
/// Parse an uppercase or lowercase Roman numeral.
pub fn parse(input : String) -> Result[Int, ParseError] {
let chars = match normalize_input(input) {
Ok(chars) => chars
Err(error) => return Err(error)
}
match validate_repetitions(chars) {
Ok(_) => ()
Err(error) => return Err(error)
}
let mut value = 0
let mut index = 0
let mut previous_token = 4000
while index < chars.length() {
let current = match symbol_value(chars[index]) {
Some(current) => current
None => return Err(InvalidCharacter(index, chars[index]))
}
if index + 1 < chars.length() {
let next = match symbol_value(chars[index + 1]) {
Some(next) => next
None => return Err(InvalidCharacter(index + 1, chars[index + 1]))
}
if current < next {
if !is_subtractive_pair(chars[index], chars[index + 1]) {
return Err(
InvalidSubtractivePair(index, chars[index], chars[index + 1]),
)
}
let token_value = next - current
if token_value > previous_token {
return Err(InvalidOrder(index))
}
value = value + token_value
previous_token = token_value
index = index + 2
continue
}
}
if current > previous_token {
return Err(InvalidOrder(index))
}
value = value + current
previous_token = current
index = index + 1
}
match format(value) {
Ok(expected) =>
if String::from_array(chars) == expected {
Ok(value)
} else {
Err(NonCanonical(expected))
}
Err(_) => Err(ParseOutOfRange(value))
}
}