use std::collections::HashMap;
pub const OP_NUM_OPCODES: usize = 158;
pub const OP_VARIABLE_LENGTH: u8 = 99;
#[rustfmt::skip]
pub const OP_PARAMS: [u8; OP_NUM_OPCODES] = [
99,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,0,0,0,
0,0,1,1,1,1,0,0,1,1,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,
0,1,1,0,0,0,1,1,0,1,1,1,1,1,0,1,0,0,0,0,0,99,99,0,0,1,0,2,1,0,2,2,2,2,3,
3,3,3,4,4,4,4,5,5,5,5,2,2,2,2,
];
#[must_use]
pub fn operand_cells(opcode: i32) -> Option<u8> {
let params = *OP_PARAMS.get(usize::try_from(opcode).ok()?)?;
(params != OP_VARIABLE_LENGTH).then_some(params)
}
pub const STK_MARGIN: i32 = 16 * 4;
pub const OP_LOAD_PRI: i32 = 1;
pub const OP_LOAD_ALT: i32 = 2;
pub const OP_LOAD_S_PRI: i32 = 3;
pub const OP_LOAD_S_ALT: i32 = 4;
pub const OP_LOAD_I: i32 = 9;
pub const OP_LODB_I: i32 = 10;
pub const OP_CONST_PRI: i32 = 11;
pub const OP_CONST_ALT: i32 = 12;
pub const OP_ADDR_PRI: i32 = 13;
pub const OP_ADDR_ALT: i32 = 14;
pub const OP_STOR_I: i32 = 23;
pub const OP_STRB_I: i32 = 24;
pub const OP_LIDX: i32 = 25;
pub const OP_LIDX_B: i32 = 26;
pub const OP_IDXADDR: i32 = 27;
pub const OP_IDXADDR_B: i32 = 28;
pub const OP_MOVE_PRI: i32 = 33;
pub const OP_MOVE_ALT: i32 = 34;
pub const OP_XCHG: i32 = 35;
pub const OP_PUSH_PRI: i32 = 36;
pub const OP_PUSH_ALT: i32 = 37;
pub const OP_PUSH_R: i32 = 38;
pub const OP_PUSH_C: i32 = 39;
pub const OP_PUSH: i32 = 40;
pub const OP_PUSH_S: i32 = 41;
pub const OP_POP_PRI: i32 = 42;
pub const OP_POP_ALT: i32 = 43;
pub const OP_STACK: i32 = 44;
pub const OP_HEAP: i32 = 45;
pub const OP_PROC: i32 = 46;
pub const OP_CALL: i32 = 49;
pub const OP_CALL_PRI: i32 = 50;
pub const OP_PUSH_ADR: i32 = 133;
pub const OP_PUSH2_C: i32 = 138;
pub const OP_PUSH5_ADR: i32 = 153;
pub const OP_SDIV: i32 = 73;
pub const OP_SDIV_ALT: i32 = 74;
pub const OP_UDIV: i32 = 76;
pub const OP_UDIV_ALT: i32 = 77;
pub const OP_ZERO_PRI: i32 = 89;
pub const OP_ZERO_ALT: i32 = 90;
pub const OP_BOUNDS: i32 = 121;
pub const OP_BREAK: i32 = 137;
pub struct OpcodeMap {
inverse: HashMap<usize, i32>,
}
impl OpcodeMap {
#[must_use]
pub fn new(opcode_table: Option<Vec<usize>>) -> Self {
let inverse = opcode_table
.map(|table| {
table
.into_iter()
.enumerate()
.map(|(op, addr)| (addr, i32::try_from(op).unwrap_or(-1)))
.collect()
})
.unwrap_or_default();
Self { inverse }
}
#[must_use]
pub fn is_identity(&self) -> bool {
self.inverse.is_empty()
}
#[must_use]
pub fn decode(&self, raw: i32) -> Option<i32> {
if self.inverse.is_empty() {
return Some(raw);
}
if let Some(&op) = self
.inverse
.get(&usize::try_from(raw.cast_unsigned()).ok()?)
{
return Some(op);
}
(0..i32::try_from(OP_NUM_OPCODES).ok()?)
.contains(&raw)
.then_some(raw)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operand_cells_known_opcodes() {
assert_eq!(operand_cells(OP_LOAD_PRI), Some(1));
assert_eq!(operand_cells(OP_MOVE_PRI), Some(0));
assert_eq!(operand_cells(OP_PUSH5_ADR), Some(5));
}
#[test]
fn operand_cells_rejects_variable_and_out_of_range() {
assert_eq!(operand_cells(0), None);
assert_eq!(operand_cells(-1), None);
assert_eq!(operand_cells(i32::try_from(OP_NUM_OPCODES).unwrap()), None);
}
#[test]
fn decode_resolves_relocated_addresses() {
let mut table = vec![0usize; OP_NUM_OPCODES];
table[OP_SDIV as usize] = 0x1000;
table[OP_BREAK as usize] = 0x2000;
let map = OpcodeMap::new(Some(table));
assert!(!map.is_identity());
assert_eq!(map.decode(0x1000), Some(OP_SDIV));
assert_eq!(map.decode(0x2000), Some(OP_BREAK));
}
#[test]
fn decode_accepts_plain_opcode_on_relocated_image() {
let mut table = vec![0usize; OP_NUM_OPCODES];
table[OP_SDIV as usize] = 0x1000;
let map = OpcodeMap::new(Some(table));
assert_eq!(map.decode(OP_BOUNDS), Some(OP_BOUNDS));
assert_eq!(map.decode(0x7fff_0000), None);
}
#[test]
fn decode_is_identity_without_a_table() {
let map = OpcodeMap::new(None);
assert!(map.is_identity());
assert_eq!(map.decode(OP_BOUNDS), Some(OP_BOUNDS));
assert_eq!(map.decode(0x7fff_0000), Some(0x7fff_0000));
}
}