///|
// Maximum `UInt64` value that can be represented by MoonBit `Int` in this
// package's offset math.
let max_int_u64 : UInt64 = 0x7fffffffUL
///|
// Maximum `UInt` value that can be reinterpreted safely as a non-negative `Int`.
let max_int_uint : UInt = 0x7fffffffU
///|
// Keep the limit explicit so checked math does not depend on backend details.
let max_int_value : Int = 0x7fffffff
///|
// Checked byte-offset addition used before every slice boundary calculation.
fn checked_add(a : Int, b : Int) -> Int raise ParseError {
if a < 0 || b < 0 || a > max_int_value - b {
raise IntegerOverflow
}
a + b
}
///|
// Checked byte-size multiplication used for table-length calculations.
fn checked_mul(a : Int, b : Int) -> Int raise ParseError {
if a < 0 || b < 0 {
raise IntegerOverflow
}
if b != 0 && a > max_int_value / b {
raise IntegerOverflow
}
a * b
}
///|
// Convert a parsed ELF64 offset or size into a MoonBit slice index.
fn u64_to_int_checked(value : UInt64) -> Int raise ParseError {
if value > max_int_u64 {
raise IntegerOverflow
}
value.to_int()
}
///|
// Convert a parsed ELF32 count or index into a MoonBit slice index.
fn uint_to_int_checked(value : UInt) -> Int raise ParseError {
if value > max_int_uint {
raise IntegerOverflow
}
value.reinterpret_as_int()
}
///|
// Bounds-checked slicing wrapper that normalizes failures to ParseError.
fn slice_checked(
data : BytesView,
start : Int,
end : Int,
) -> BytesView raise ParseError {
if start < 0 || end < start || end > data.length() {
raise SliceReadError(start, end)
}
data[start:end]
}
///|
// Return `data[start:]` after checking the start offset.
fn tail_checked(data : BytesView, start : Int) -> BytesView raise ParseError {
if start < 0 || start > data.length() {
raise SliceReadError(start, data.length())
}
data[start:]
}
///|
/// Trait implemented by structures that can parse themselves from ELF bytes.
///
/// Implementations receive the file byte order, ELF class, byte offset, and the
/// source byte slice. They return the parsed value and the next offset. This is
/// the small abstraction used by every lazy table wrapper in the package.
pub(open) trait ParseAt {
/// Parse one value at `offset` from `BytesView`, using the supplied class and endian.
parse_at(Endian, Class, Int, BytesView) -> (Self, Int) raise ParseError
}
///|
// Ensure that an on-disk table entry size matches the parser's expected size.
fn validate_entsize(entsize : Int, expected : Int) -> Int raise ParseError {
if entsize != expected {
raise BadEntsize(entsize.to_uint64(), expected.to_uint64())
}
entsize
}
///|
/// Lazy parsing table over a contiguous sequence of fixed-size ELF records.
///
/// A `ParsingTable` keeps only the source bytes, layout information, and parser
/// callback. Individual entries are decoded when `get` or `iter` requests them,
/// which avoids eagerly materializing large section, segment, or symbol tables.
pub struct ParsingTable[T] {
/// Byte order of the file that owns the table.
endian : Endian
/// ELF32 or ELF64 record layout.
class : Class
/// Raw byte range containing the table.
data : BytesView
/// Size in bytes of one table entry.
entry_size : Int
/// Parser function for one entry.
parser : (Endian, Class, Int, BytesView) -> (T, Int) raise ParseError
/// Type witness used only to retain the generic parameter.
phantom : T?
}
///|
fn[T] ParsingTable::ParsingTable(
endian : Endian,
class : Class,
data : BytesView,
entry_size : Int,
parser : (Endian, Class, Int, BytesView) -> (T, Int) raise ParseError,
) -> ParsingTable[T] {
{ endian, class, data, entry_size, parser, phantom: None }
}
///|
/// Number of entries in the table.
///
/// A non-positive entry size is treated as an empty table to avoid division by
/// zero on malformed internal construction.
pub fn[T] ParsingTable::len(self : ParsingTable[T]) -> Int {
if self.entry_size <= 0 {
0
} else {
self.data.length() / self.entry_size
}
}
///|
/// Returns `true` when `len` is zero.
pub fn[T] ParsingTable::is_empty(self : ParsingTable[T]) -> Bool {
self.len() == 0
}
///|
/// Parse and return the entry at `index`.
///
/// Raises `BadOffset` for negative or out-of-range indices, and propagates any
/// parse error produced by the entry parser.
pub fn[T] ParsingTable::get(
self : ParsingTable[T],
index : Int,
) -> T raise ParseError {
if index < 0 {
raise BadOffset(0UL)
}
if self.data.is_empty() {
raise BadOffset(index.to_uint64())
}
let start = checked_mul(index, self.entry_size)
if start >= self.data.length() {
raise BadOffset(index.to_uint64())
}
let (value, _) = (self.parser)(self.endian, self.class, start, self.data)
value
}
///|
/// Iterate over the table, parsing entries on demand.
///
/// If a parse error is encountered, iteration stops. Use `get` when the caller
/// needs the exact parse error for a specific entry.
pub fn[T] ParsingTable::iter(self : ParsingTable[T]) -> Iter[T] {
let idx = Ref(0)
let failed = Ref(false)
Iter::new(
fn() {
if failed.val || idx.val >= self.len() {
None
} else {
let i = idx.val
idx.val += 1
try self.get(i) catch {
_ => {
failed.val = true
None
}
} noraise {
value => Some(value)
}
}
},
size_hint=self.len(),
)
}