#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use super::stream::InstructionStream;
use super::types::Instruction;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct JumpTable {
targets: Vec<usize>,
}
impl JumpTable {
pub fn new(targets: Vec<usize>) -> Self {
Self { targets }
}
pub fn scan(code: &[u8]) -> Self {
let mut targets = Vec::new();
let mut stream = InstructionStream::new(code);
while let Some(item) = stream.next_instruction() {
if let Ok((pos, _label, Instruction::Target {})) = item {
targets.push(pos);
}
}
Self { targets }
}
pub fn get(&self, label: u16) -> Option<usize> {
self.targets.get(usize::from(label)).copied()
}
pub fn len(&self) -> usize {
self.targets.len()
}
pub fn is_empty(&self) -> bool {
self.targets.is_empty()
}
pub fn targets(&self) -> &[usize] {
&self.targets
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::codec;
fn assemble(instrs: &[Instruction]) -> Vec<u8> {
instrs.iter().flat_map(codec::encode).collect()
}
#[test]
fn empty_buffer_has_no_targets() {
let table = JumpTable::scan(&[]);
assert!(table.is_empty());
assert_eq!(table.len(), 0);
assert_eq!(table.get(0), None);
}
#[test]
fn buffer_with_no_targets_has_empty_table() {
let buf = assemble(&[Instruction::Push1 { val: [42] }, Instruction::Halt {}]);
let table = JumpTable::scan(&buf);
assert!(table.is_empty());
}
#[test]
fn single_target_at_offset_zero() {
let buf = assemble(&[Instruction::Target {}, Instruction::Halt {}]);
let table = JumpTable::scan(&buf);
assert_eq!(table.len(), 1);
assert_eq!(table.get(0), Some(0));
}
#[test]
fn target_after_other_instructions() {
let buf = assemble(&[
Instruction::Push1 { val: [7] },
Instruction::Target {},
Instruction::Halt {},
]);
let table = JumpTable::scan(&buf);
assert_eq!(table.len(), 1);
assert_eq!(table.get(0), Some(2));
}
#[test]
fn multiple_targets_get_sequential_ids() {
let buf = assemble(&[
Instruction::Target {},
Instruction::Nop {},
Instruction::Target {},
Instruction::Nop {},
Instruction::Target {},
Instruction::Halt {},
]);
let table = JumpTable::scan(&buf);
assert_eq!(table.len(), 3);
assert_eq!(table.get(0), Some(0));
assert_eq!(table.get(1), Some(2));
assert_eq!(table.get(2), Some(4));
assert_eq!(table.get(3), None);
}
#[test]
fn explicit_constructor_round_trips_through_get() {
let table = JumpTable::new(vec![10, 20, 30]);
assert_eq!(table.len(), 3);
assert_eq!(table.get(0), Some(10));
assert_eq!(table.get(1), Some(20));
assert_eq!(table.get(2), Some(30));
assert_eq!(table.get(3), None);
assert_eq!(table.targets(), &[10, 20, 30]);
}
}