use std::error::Error;
use std::fmt;
use crate::atom::AtomTable;
use crate::loader::ParsedModule;
use super::chunks::{
encode_atom_chunk, encode_export_chunk, encode_import_chunk, encode_lambda_chunk,
encode_line_chunk, encode_string_chunk,
};
use super::code::encode_code_chunk;
use super::compact::AtomEncoder;
use super::literals::encode_literal_chunk;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncodeError {
AtomNotInTable,
ValueOutOfRange,
MalformedBigInteger,
UnsupportedInstruction,
CompressionFailed,
}
impl fmt::Display for EncodeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::AtomNotInTable => "operand atom is absent from the module atom table",
Self::ValueOutOfRange => "value exceeds the width of its BEAM chunk field",
Self::MalformedBigInteger => "big-integer literal is not a sign byte plus magnitude",
Self::UnsupportedInstruction => "instruction shape cannot be encoded",
Self::CompressionFailed => "literal table zlib compression failed",
};
formatter.write_str(message)
}
}
impl Error for EncodeError {}
pub fn encode_module(
module: &ParsedModule,
atom_table: &AtomTable,
) -> Result<Vec<u8>, EncodeError> {
let encoder = AtomEncoder::new(&module.atoms, atom_table);
let mut chunks: Vec<(&[u8; 4], Vec<u8>)> = Vec::new();
chunks.push((b"AtU8", encode_atom_chunk(&module.atoms, &encoder)?));
chunks.push((b"Code", encode_code_chunk(&module.instructions, &encoder)?));
if !module.imports.is_empty() {
chunks.push((b"ImpT", encode_import_chunk(&module.imports, &encoder)?));
}
if !module.exports.is_empty() {
chunks.push((b"ExpT", encode_export_chunk(&module.exports, &encoder)?));
}
if !module.lambdas.is_empty() {
chunks.push((b"FunT", encode_lambda_chunk(&module.lambdas, &encoder)?));
}
if let Some(literal_chunk) = encode_literal_chunk(&module.literals, &encoder)? {
chunks.push((b"LitT", literal_chunk));
}
if !module.string_table.is_empty() {
chunks.push((b"StrT", encode_string_chunk(&module.string_table)));
}
if !module.line_info.is_empty() {
chunks.push((b"Line", encode_line_chunk(&module.line_info)?));
}
Ok(frame_container(&chunks))
}
fn frame_container(chunks: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
let mut body = Vec::new();
body.extend_from_slice(b"BEAM");
for (tag, chunk) in chunks {
body.extend_from_slice(*tag);
body.extend_from_slice(&(chunk.len() as u32).to_be_bytes());
body.extend_from_slice(chunk);
let padding = (4 - (chunk.len() % 4)) % 4;
body.resize(body.len() + padding, 0);
}
let mut out = Vec::with_capacity(8 + body.len());
out.extend_from_slice(b"FOR1");
out.extend_from_slice(&(body.len() as u32).to_be_bytes());
out.extend_from_slice(&body);
out
}