///| Structural parser for WebAssembly binary modules. It validates the file
///| header, section framing, known section IDs, and standard-section ordering
///|
/// before any section-specific decoder is invoked.
fn read_header_byte(
cursor : Cursor,
expected : Int,
label : String,
) -> Result[Unit, DecodeError] {
match cursor.read_u8() {
Err(error) => Err(error)
Ok(actual) =>
if actual == expected {
Ok(())
} else {
Err(
InvalidHeader(
label +
" expected " +
expected.to_string() +
" but found " +
actual.to_string(),
),
)
}
}
}
///|
fn parse_header(cursor : Cursor) -> Result[Int, DecodeError] {
match read_header_byte(cursor, 0x00, "magic[0]") {
Err(error) => return Err(error)
Ok(_) => ()
}
match read_header_byte(cursor, 0x61, "magic[1]") {
Err(error) => return Err(error)
Ok(_) => ()
}
match read_header_byte(cursor, 0x73, "magic[2]") {
Err(error) => return Err(error)
Ok(_) => ()
}
match read_header_byte(cursor, 0x6d, "magic[3]") {
Err(error) => return Err(error)
Ok(_) => ()
}
let mut version = 0
let mut shift = 0
while shift < 32 {
match cursor.read_u8() {
Err(error) => return Err(error)
Ok(byte) => version = version | (byte << shift)
}
shift = shift + 8
}
if version != 1 {
Err(
InvalidHeader(
"only binary format version 1 is supported, found " +
version.to_string(),
),
)
} else {
Ok(version)
}
}
///|
fn check_section_order(
kind : SectionKind,
previous : Int,
position : Int,
) -> Result[Int, DecodeError] {
if kind == Custom {
Ok(previous)
} else if kind.id() <= previous {
Err(
InvalidSection(
kind.id(),
position,
"standard sections must be strictly ordered and unique",
),
)
} else {
Ok(kind.id())
}
}
///|
fn custom_section_name(payload : Cursor) -> Result[String?, DecodeError] {
if payload.is_finished() {
Err(
ValidationError(payload.position, "custom section must begin with a name"),
)
} else {
match payload.read_name() {
Ok(name) => Ok(Some(name))
Err(error) => Err(error)
}
}
}
///|
pub fn parse_module(bytes : Array[Int]) -> Result[Module, DecodeError] {
let cursor = Cursor::new(bytes)
let version = match parse_header(cursor) {
Ok(value) => value
Err(error) => return Err(error)
}
let sections : Array[Section] = []
let mut previous_standard = 0
while !cursor.is_finished() {
let full_start = cursor.position
let id = match cursor.read_u8() {
Ok(value) => value
Err(error) => return Err(error)
}
let kind = match section_kind_from_id(id) {
Some(value) => value
None => return Err(InvalidSection(id, full_start, "unknown section id"))
}
previous_standard = match
check_section_order(kind, previous_standard, full_start) {
Ok(value) => value
Err(error) => return Err(error)
}
let declared_size = match cursor.read_var_u32() {
Ok(value) => value
Err(error) => return Err(error)
}
let payload_start = cursor.position
let payload_cursor = match cursor.subcursor(declared_size) {
Ok(value) => value
Err(_) =>
return Err(
InvalidSection(
id, full_start, "declared payload extends past end of module",
),
)
}
let custom_name = if kind == Custom {
match custom_section_name(payload_cursor) {
Ok(value) => value
Err(error) => return Err(error)
}
} else {
None
}
sections.push({
id,
kind,
payload: { start: payload_start, end: payload_start + declared_size },
full: { start: full_start, end: cursor.position },
custom_name,
})
}
Ok({ bytes, sections, version })
}
///|
pub struct ModuleStats {
byte_length : Int
section_count : Int
custom_section_count : Int
payload_bytes : Int
}
///|
pub fn ModuleStats::description(self : ModuleStats) -> String {
"module has " +
self.section_count.to_string() +
" sections (" +
self.custom_section_count.to_string() +
" custom), " +
self.byte_length.to_string() +
" bytes total and " +
self.payload_bytes.to_string() +
" payload bytes"
}
///|
pub fn Module::stats(self : Module) -> ModuleStats {
let mut custom = 0
let mut payload = 0
for section in self.sections {
payload = payload + section.payload_size()
if section.kind == Custom {
custom = custom + 1
}
}
{
byte_length: self.byte_length(),
section_count: self.section_count(),
custom_section_count: custom,
payload_bytes: payload,
}
}
///|
pub fn Module::has_section(self : Module, kind : SectionKind) -> Bool {
match self.find_section(kind) {
Some(_) => true
None => false
}
}
///|
pub fn Module::section_labels(self : Module) -> Array[String] {
let labels : Array[String] = []
for section in self.sections {
let label = match section.custom_name {
Some(name) => "custom:" + name
None => section.kind.label()
}
labels.push(label)
}
labels
}