///|
fn contains_supported_unicode_roman(input : String) -> Bool {
for character in input.to_array() {
if character >= '\u{2160}' && character <= '\u{217f}' {
return true
}
}
false
}
///|
fn has_outer_whitespace(input : String) -> Bool {
let chars = input.to_array()
if chars.length() == 0 {
return false
}
is_outer_whitespace(chars[0]) ||
is_outer_whitespace(chars[chars.length() - 1])
}
///|
fn analysis_content_span(input : String, config : ParseConfig) -> SourceSpan {
let chars = input.to_array()
if chars.length() == 0 {
return { start: 0, end: 0 }
}
let bounds = normalized_content_bounds(chars, config.trim_outer_whitespace)
if bounds.0 >= bounds.1 {
{ start: 0, end: chars.length() }
} else {
{ start: bounds.0, end: bounds.1 }
}
}
///|
fn diagnostic_message(code : DiagnosticCode) -> String {
match code {
EmptyInputCode => "Roman numeral input is empty"
InvalidConfigurationCode => "Roman processing configuration is invalid"
UppercaseRequiredCode => "Roman numeral source must use uppercase letters"
LowercaseRequiredCode => "Roman numeral source must use lowercase letters"
UnicodeSourceNotAllowedCode =>
"Unicode Roman compatibility source characters are disabled"
OuterWhitespaceNotAllowedCode =>
"outer whitespace is disabled by lint policy"
SingleSymbolExcludedCode => "single-symbol Roman candidates are disabled"
CandidateTooLongCode =>
"Roman candidate exceeds the configured length limit"
UnsupportedCharacterCode => "input contains an unsupported Roman character"
LowercaseNotAllowedCode => "lowercase Roman letters are disabled"
InvalidRepetitionCode => "Roman symbol repetition is invalid"
InvalidSubtractionCode => "Roman subtractive pair is invalid"
InvalidOrderCode => "Roman symbols are in an invalid order"
MalformedParenthesesCode => "parenthesized-thousands structure is malformed"
NestedParenthesesCode => "nested Roman parentheses are not supported"
EmptyParenthesizedGroupCode => "parenthesized-thousands group is empty"
NonCanonicalCode =>
"Roman numeral spelling is not canonical for the selected profile"
ValueOutOfRangeCode =>
"Roman numeral value is outside the selected profile range"
}
}
///|
fn diagnostic_from_report_error(
original : String,
content_span : SourceSpan,
error : RomanReportError,
) -> RomanDiagnostic {
let mapped : (DiagnosticCode, SourceSpan, String?) = match error {
EmptyRomanInput =>
(EmptyInputCode, { start: 0, end: original.to_array().length() }, None)
UnsupportedRomanCharacter(error_span, _) =>
(UnsupportedCharacterCode, error_span, None)
LowercaseRomanNotAllowed(error_span, character) =>
(
LowercaseNotAllowedCode,
error_span,
Some(String::from_array([character.to_ascii_uppercase()])),
)
InvalidRomanGrammar(error_span, grammar_code) =>
(grammar_code, error_span, None)
NonCanonicalRoman(expected) =>
(NonCanonicalCode, content_span, Some(expected))
RomanReportOutOfRange(_) => (ValueOutOfRangeCode, content_span, None)
InvalidRomanConfiguration(_) =>
(
InvalidConfigurationCode,
{ start: 0, end: original.to_array().length() },
None,
)
}
{
code: mapped.0,
severity: DiagnosticError,
message: diagnostic_message(mapped.0),
span: mapped.1,
replacement: mapped.2,
}
}
///|
/// Analyze configured input and retain diagnostics for rejected values.
pub fn analyze_roman(
input : String,
config : ParseConfig,
) -> RomanAnalysisReport {
let observed_unicode_compatibility = contains_supported_unicode_roman(input)
let observed_outer_whitespace = has_outer_whitespace(input)
match parse_with_config(input, config) {
Ok(report) =>
{
original: input,
config,
status: AnalysisAccepted(report),
diagnostics: [],
replacement: None,
observed_unicode_compatibility,
observed_outer_whitespace,
}
Err(error) => {
let diagnostic = diagnostic_from_report_error(
input,
analysis_content_span(input, config),
error,
)
{
original: input,
config,
status: AnalysisRejected(error),
diagnostics: [diagnostic],
replacement: diagnostic.replacement,
observed_unicode_compatibility,
observed_outer_whitespace,
}
}
}
}