#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use thiserror::Error;
use super::jump_table::JumpTable;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Program {
code: Vec<u8>,
jump_table: JumpTable,
}
impl Program {
pub fn new(code: Vec<u8>) -> Self {
let jump_table = JumpTable::scan(&code);
Self { code, jump_table }
}
pub fn from_parts(code: Vec<u8>, jump_table: JumpTable) -> Self {
Self { code, jump_table }
}
pub fn jump_table(&self) -> &JumpTable {
&self.jump_table
}
pub fn code(&self) -> &[u8] {
&self.code
}
pub fn encode(&self) -> Vec<u8> {
self.code.clone()
}
pub fn decode(bytes: &[u8]) -> Result<Self, ProgramDecodeError> {
Ok(Self::new(bytes.to_vec()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum ProgramDecodeError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::Instruction;
use crate::bytecode::codec;
fn assemble(instrs: &[Instruction]) -> Vec<u8> {
instrs.iter().flat_map(codec::encode).collect()
}
#[test]
fn encode_is_just_the_code() {
let prog = Program::new(vec![0xFF]);
assert_eq!(prog.encode(), vec![0xFF]);
}
#[test]
fn decode_round_trips() {
let prog = Program::new(vec![0xFF, 0x00]);
let decoded = Program::decode(&prog.encode()).unwrap();
assert_eq!(decoded.code(), &[0xFF, 0x00]);
}
#[test]
fn empty_program_has_empty_jump_table() {
let prog = Program::new(vec![]);
assert!(prog.jump_table().is_empty());
assert!(prog.encode().is_empty());
}
#[test]
fn jump_table_is_built_from_targets_in_code() {
let buf = assemble(&[
Instruction::Target {},
Instruction::Nop {},
Instruction::Target {},
Instruction::Halt {},
]);
let prog = Program::new(buf);
let table = prog.jump_table();
assert_eq!(table.len(), 2);
assert_eq!(table.get(0), Some(0));
assert_eq!(table.get(1), Some(2));
}
}