///|
fn append_roman_mapping_segment(
mappings : Array[RomanEditMappingSegment],
kind : RomanEditMappingKind,
source_start : Int,
source_end : Int,
output_start : Int,
output_end : Int,
) -> Unit {
mappings.push({
kind,
source_span: { start: source_start, end: source_end },
output_span: { start: output_start, end: output_end },
})
}
///|
fn calculate_roman_edit_application(
plan : RomanEditPlan,
materialize_output : Bool,
) -> (RomanEditPreview, String) {
let source_chars = plan.original.to_array()
let applied_edits : Array[RomanAppliedEdit] = []
let mapping_segments : Array[RomanEditMappingSegment] = []
let mut source_cursor = 0
let mut output_cursor = 0
let mut output = ""
for edit in plan.edits {
if source_cursor < edit.span.start {
let unchanged_length = edit.span.start - source_cursor
append_roman_mapping_segment(
mapping_segments,
RomanUnchangedMapping,
source_cursor,
edit.span.start,
output_cursor,
output_cursor + unchanged_length,
)
if materialize_output {
output = output +
scan_substring(source_chars, source_cursor, edit.span.start)
}
output_cursor = output_cursor + unchanged_length
}
let replacement_length = edit.replacement.to_array().length()
let output_span : SourceSpan = {
start: output_cursor,
end: output_cursor + replacement_length,
}
append_roman_mapping_segment(
mapping_segments,
RomanReplacedMapping(edit.id),
edit.span.start,
edit.span.end,
output_span.start,
output_span.end,
)
applied_edits.push({
id: edit.id,
source_span: edit.span,
output_span,
source_text: edit.expected_source,
replacement_text: edit.replacement,
numeric_value: edit.numeric_value,
reason: edit.reason,
source_diagnostic_code: edit.source_diagnostic_code,
})
if materialize_output {
output = output + edit.replacement
}
source_cursor = edit.span.end
output_cursor = output_span.end
}
if source_cursor < source_chars.length() {
let unchanged_length = source_chars.length() - source_cursor
append_roman_mapping_segment(
mapping_segments,
RomanUnchangedMapping,
source_cursor,
source_chars.length(),
output_cursor,
output_cursor + unchanged_length,
)
if materialize_output {
output = output +
scan_substring(source_chars, source_cursor, source_chars.length())
}
}
(
{
original: plan.original,
applied_edits,
mapping_segments,
statistics: plan.statistics,
},
output,
)
}
///|
fn revalidate_roman_edit_plan(
plan : RomanEditPlan,
) -> Result[RomanEditPlan, RomanEditError] {
match validate_roman_edit_plan(plan.original, plan.edits, plan.config) {
Err(error) => Err(error)
Ok(validated) => Ok({ ..validated, diagnostics: plan.diagnostics })
}
}
///|
/// Revalidate a plan before previewing exact output spans and mappings.
pub fn preview_roman_edit_plan(
plan : RomanEditPlan,
) -> Result[RomanEditPreview, RomanEditError] {
match revalidate_roman_edit_plan(plan) {
Err(error) => Err(error)
Ok(validated) => Ok(calculate_roman_edit_application(validated, false).0)
}
}
///|
/// Apply every validated edit atomically when the complete source is current.
pub fn apply_roman_edit_plan(
source : String,
plan : RomanEditPlan,
) -> Result[RomanEditApplicationReport, RomanEditError] {
if source != plan.original {
return Err(RomanEditPlanSourceMismatch(plan.original, source))
}
let validated = match revalidate_roman_edit_plan(plan) {
Err(error) => return Err(error)
Ok(validated) => validated
}
let calculated = calculate_roman_edit_application(validated, true)
let preview = calculated.0
Ok({
original: validated.original,
output: calculated.1,
applied_edits: preview.applied_edits,
mapping_segments: preview.mapping_segments,
statistics: preview.statistics,
})
}