// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0
///|
priv struct MapReader {
data : BytesView
base : Int32
file_offset : UInt64
mut position : Int32
}
///|
fn MapReader::remaining(self : MapReader) -> Int32 {
self.data.length() - self.position
}
///|
fn MapReader::invalid(self : MapReader, reason : String) -> DecodeError {
InvalidTableMap(
file_offset=self.file_offset,
body_offset=self.base + self.position,
reason~,
)
}
///|
fn MapReader::take(
self : MapReader,
length : Int32,
) -> BytesView raise DecodeError {
guard length >= 0 && length <= self.remaining() else {
raise self.invalid("truncated table-map field")
}
let start = self.position
self.position += length
self.data[start:self.position]
}
///|
fn MapReader::byte(self : MapReader) -> Byte raise DecodeError {
self.take(1)[0]
}
///|
fn MapReader::uint_le(
self : MapReader,
width : Int32,
) -> UInt64 raise DecodeError {
let bytes = self.take(width)
let mut value = 0UL
for i in 0.. UInt64 raise DecodeError {
match self.byte() {
0x00..<=0xfa as value => value.to_uint().to_uint64()
0xfc => self.uint_le(2)
0xfd => self.uint_le(3)
0xfe => self.uint_le(8)
_ => raise self.invalid("NULL or reserved packed-length marker")
}
}
///|
fn MapReader::sized_field(self : MapReader) -> BytesView raise DecodeError {
let length = self.packed_length()
guard length <= self.remaining().reinterpret_as_uint().to_uint64() else {
raise self.invalid("packed length exceeds remaining payload")
}
self.take(length.to_int())
}
///|
fn MapReader::name(self : MapReader) -> String raise DecodeError {
let length = self.byte().to_int()
guard length > 0 else { raise self.invalid("empty database or table name") }
let bytes = self.take(length)
for byte in bytes {
guard byte != 0 else { raise self.invalid("NUL inside identifier") }
}
guard self.byte() == 0 else {
raise self.invalid("missing identifier terminator")
}
@utf8.decode(bytes) catch {
_ => raise self.invalid("identifier is not valid UTF-8")
}
}