///|
/// Structural roles of archive records.
pub(all) enum MemberKind {
Regular
SymbolTable
NameTable
} derive(Eq, Debug)
///|
/// Blank numeric fields, used by special archive records, are represented by None.
pub(all) struct Metadata {
timestamp : Int64?
uid : Int64?
gid : Int64?
mode : Int64?
} derive(Eq, Debug)
///|
/// A record in physical archive order. Names are bytes, not assumed to be UTF-8.
/// `size` excludes a BSD embedded name; `stored_size` is the header's size field.
pub(all) struct Member {
name : Bytes
kind : MemberKind
header_offset : Int
data_offset : Int
size : Int
stored_size : Int
metadata : Metadata
} derive(Eq, Debug)
///|
/// An in-memory archive, including special records in physical order.
struct Archive {
data : Bytes
records : Array[Member]
indexes : Array[SymbolIndex]
} derive(Debug)
///|
/// A validated symbol-to-member reference. Offsets refer to the original input.
pub(all) struct Symbol {
name : Bytes
member_index : Int
name_offset : Int
offset_field_offset : Int
} derive(Eq, Debug)
///|
/// Format is gnu32/gnu64 or bsd32/bsd64 with a -le, -be or -neutral suffix.
pub(all) struct SymbolIndex {
record_index : Int
format : String
symbols : Array[Symbol]
} derive(Eq, Debug)
///|
/// Return copies so changing a returned array cannot alter validated references.
pub fn Archive::symbol_indexes(self : Archive) -> Array[SymbolIndex] {
self.indexes.map(fn(index) { { ..index, symbols: index.symbols.copy(), } })
}
///|
pub extend Symbol with Eq::{equal, not_equal}
///|
pub extend Symbol with Debug::{to_repr}
///|
pub extend SymbolIndex with Eq::{equal, not_equal}
///|
pub extend SymbolIndex with Debug::{to_repr}
///|
/// A byte offset and stable code explain why parsing stopped.
pub(all) struct ParseError {
code : String
offset : Int
message : String
} derive(Eq, Debug)
///|
/// Lossy display only. Use `name` or `name_hex` for lossless identity.
pub fn Member::name_text(self : Member) -> String {
@utf8.decode_lossy(self.name[:], ignore_bom=false)
}
///|
/// A lossless name suitable for JSON and comparison keys.
pub fn Member::name_hex(self : Member) -> String {
hex_bytes(self.name)
}
///|
fn hex_bytes(bytes : Bytes) -> String {
let digits : Array[Char] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
]
let result = StringBuilder()
for byte in bytes {
let n = byte.to_int()
result.write_char(digits[n / 16])
result.write_char(digits[n % 16])
}
result.to_string()
}
///|
/// Return an owned copy of one record's payload, or None for an invalid index.
pub fn Archive::member_bytes(self : Archive, index : Int) -> Bytes? {
guard self.records.get(index) is Some(entry) else { return None }
Some(self.data[entry.data_offset:entry.data_offset + entry.size].to_owned())
}
///|
pub fn Archive::byte_length(self : Archive) -> Int {
self.data.length()
}
///|
/// Number of physical records, including name and symbol tables.
pub fn Archive::member_count(self : Archive) -> Int {
self.records.length()
}
///|
/// Return one immutable record, or None for an invalid index.
pub fn Archive::get_member(self : Archive, index : Int) -> Member? {
self.records.get(index)
}
///|
pub extend MemberKind with Eq::{equal, not_equal}
///|
pub extend Metadata with Eq::{equal, not_equal}
///|
pub extend Member with Eq::{equal, not_equal}
///|
pub extend ParseError with Eq::{equal, not_equal}
///|
pub extend MemberKind with Debug::{to_repr}
///|
pub extend Metadata with Debug::{to_repr}
///|
pub extend Member with Debug::{to_repr}
///|
pub extend ParseError with Debug::{to_repr}
///|
pub extend Archive with Debug::{to_repr}