///|
fn mapping_vlq(
input : String,
offset : Int,
line : Int,
segment : Int,
) -> VlqValue raise SourceMapError {
decode_vlq_at(input, offset~) catch {
InvalidVlq(offset=bad_offset, message~) =>
raise InvalidMappings(line~, segment~, offset=bad_offset, message~)
other => raise other
}
}
///|
fn invalid_segment(
line : Int,
segment : Int,
offset : Int,
message : String,
) -> SourceMapError {
InvalidMappings(line~, segment~, offset~, message~)
}
///|
/// Decode the ECMA-426 `mappings` field.
///
/// The decoder is strict: segments must contain exactly one, four, or five
/// fields; all accumulated coordinates and indices must remain non-negative;
/// and source/name indices must remain inside the caller-provided tables.
pub fn decode_mappings(
input : String,
sources_count~ : Int,
names~ : ArrayView[String],
) -> Array[Mapping] raise SourceMapError {
if sources_count < 0 {
raise InvalidDocument(message="sources_count must be non-negative")
}
let mappings : Array[Mapping] = []
let mut cursor = 0
let mut generated_line = 0
let mut generated_column = 0
let mut source_index = 0
let mut original_line = 0
let mut original_column = 0
let mut name_index = 0
let mut segment_index = 0
let mut expect_segment = true
while cursor < input.length() {
let ch = match input.get_char(cursor) {
Some(ch) => ch
None =>
raise invalid_segment(
generated_line, segment_index, cursor, "invalid UTF-16 boundary in mappings",
)
}
if ch == ';' {
generated_line = generated_line + 1
generated_column = 0
segment_index = 0
expect_segment = true
cursor = cursor + 1
continue
}
if ch == ',' {
if expect_segment {
raise invalid_segment(
generated_line, segment_index, cursor, "empty mapping segment",
)
}
segment_index = segment_index + 1
expect_segment = true
cursor = cursor + 1
continue
}
let segment_offset = cursor
let fields : Array[Int] = []
while cursor < input.length() {
let current = match input.get_char(cursor) {
Some(current) => current
None =>
raise invalid_segment(
generated_line, segment_index, cursor, "invalid UTF-16 boundary in mappings",
)
}
if current == ',' || current == ';' {
break
}
let decoded = mapping_vlq(input, cursor, generated_line, segment_index)
fields.push(decoded.value)
cursor = decoded.next_offset
}
if fields.length() != 1 && fields.length() != 4 && fields.length() != 5 {
raise invalid_segment(
generated_line, segment_index, segment_offset, "mapping segment must contain 1, 4, or 5 fields",
)
}
generated_column = generated_column + fields[0]
if generated_column < 0 {
raise invalid_segment(
generated_line, segment_index, segment_offset, "generated column became negative",
)
}
let generated = Position::new(line=generated_line, column=generated_column)
if fields.length() == 1 {
mappings.push(Mapping::generated_only(generated~))
} else {
source_index = source_index + fields[1]
original_line = original_line + fields[2]
original_column = original_column + fields[3]
if source_index < 0 || source_index >= sources_count {
raise invalid_segment(
generated_line, segment_index, segment_offset, "source index is outside the sources table",
)
}
if original_line < 0 || original_column < 0 {
raise invalid_segment(
generated_line, segment_index, segment_offset, "original position became negative",
)
}
let original = OriginalPosition::new(
source_index~,
line=original_line,
column=original_column,
)
let name = if fields.length() == 5 {
name_index = name_index + fields[4]
if name_index < 0 || name_index >= names.length() {
raise invalid_segment(
generated_line, segment_index, segment_offset, "name index is outside the names table",
)
}
Some(names[name_index])
} else {
None
}
mappings.push(Mapping::mapped(generated~, original~, name?))
}
expect_segment = false
}
if expect_segment && input.length() > 0 {
let last = input.get_char(input.length() - 1)
if last == Some(',') {
raise invalid_segment(
generated_line,
segment_index,
input.length() - 1,
"trailing comma creates an empty mapping segment",
)
}
}
mappings
}
///|
/// Count generated lines represented by a mappings string.
pub fn mappings_line_count(input : String) -> Int {
let mut lines = 1
for ch in input {
if ch == ';' {
lines = lines + 1
}
}
lines
}