///|
priv struct WireInstruction {
kind : InstructionKind
size : Int
mode : Int
}
///|
priv struct EncodedSections {
data : Bytes
instructions : Bytes
addresses : Bytes
output_size : Int
}
///|
fn make_wire_instruction(
kind : InstructionKind,
size : Int,
mode : Int,
) -> WireInstruction {
{ kind, size, mode }
}
///|
fn write_encoded_address(
section : @buffer.Buffer,
encoded : EncodedAddress,
) -> Unit raise VcdiffError {
if encoded.single_byte {
if encoded.value < 0 || encoded.value > 255 {
raise InvalidAddress(offset=-1, address=encoded.value, limit=255)
}
section.write_byte(encoded.value.to_byte())
} else {
write_varint(section, encoded.value)
}
}
///|
fn prepare_wire_instructions(
target : Bytes,
plan : WindowPlan,
data_section : @buffer.Buffer,
address_section : @buffer.Buffer,
) -> Array[WireInstruction] raise VcdiffError {
let wires : Array[WireInstruction] = []
let cache = AddressCache::new()
let mut produced = 0
for instruction in plan.instructions {
match instruction {
PlannedAdd(target_offset~, size~) => {
data_section.write_bytes(target[target_offset:target_offset + size])
wires.push(make_wire_instruction(Add, size, 0))
produced += size
}
PlannedRun(byte~, size~) => {
data_section.write_byte(byte)
wires.push(make_wire_instruction(Run, size, 0))
produced += size
}
PlannedCopy(address~, size~) => {
let here = plan.source_size + produced
let encoded = cache.encode(address, here)
write_encoded_address(address_section, encoded)
wires.push(make_wire_instruction(Copy, size, encoded.mode))
produced += size
}
}
}
if produced != plan.target_size {
raise LengthMismatch(offset=-1, expected=plan.target_size, actual=produced)
}
wires
}
///|
fn code_matches_wire(
code : CodeInstruction,
wire : WireInstruction,
require_fixed_size : Bool,
) -> Bool {
if code.kind != wire.kind || code.mode != wire.mode {
return false
}
if require_fixed_size {
code.size > 0 && code.size == wire.size
} else {
code.size == wire.size || code.size == 0
}
}
///|
fn find_pair_code(
table : Array[CodeTableEntry],
first : WireInstruction,
second : WireInstruction,
) -> Int {
for index, entry in table {
if entry.second.kind != Noop &&
code_matches_wire(entry.first, first, true) &&
code_matches_wire(entry.second, second, true) {
return index
}
}
-1
}
///|
fn find_single_code(
table : Array[CodeTableEntry],
wire : WireInstruction,
) -> Int raise VcdiffError {
let mut variable_size_code = -1
for index, entry in table {
if entry.second.kind == Noop &&
entry.first.kind == wire.kind &&
entry.first.mode == wire.mode {
if entry.first.size == wire.size && wire.size > 0 {
return index
}
if entry.first.size == 0 {
variable_size_code = index
}
}
}
if variable_size_code < 0 {
raise InvalidInstruction(
offset=-1,
reason="no default code-table entry can represent the instruction",
)
}
variable_size_code
}
///|
fn write_code_size(
section : @buffer.Buffer,
code : CodeInstruction,
wire : WireInstruction,
) -> Unit raise VcdiffError {
if code.size == 0 {
write_varint(section, wire.size)
}
}
///|
fn encode_wire_instruction_section(
wires : Array[WireInstruction],
) -> Bytes raise VcdiffError {
let table = default_code_table()
let section = @buffer.Buffer(size_hint=wires.length() * 2)
let mut index = 0
while index < wires.length() {
if index + 1 < wires.length() {
let pair_code = find_pair_code(table, wires[index], wires[index + 1])
if pair_code >= 0 {
section.write_byte(pair_code.to_byte())
index += 2
continue
}
}
let single_code = find_single_code(table, wires[index])
let entry = table[single_code]
section.write_byte(single_code.to_byte())
write_code_size(section, entry.first, wires[index])
index += 1
}
section.to_bytes()
}
///|
fn encode_window_sections(
target : Bytes,
plan : WindowPlan,
) -> EncodedSections raise VcdiffError {
let data_section = @buffer.Buffer()
let address_section = @buffer.Buffer()
let wires = prepare_wire_instructions(
target, plan, data_section, address_section,
)
let instruction_section = encode_wire_instruction_section(wires)
{
data: data_section.to_bytes(),
instructions: instruction_section,
addresses: address_section.to_bytes(),
output_size: plan.target_size,
}
}