|
|
@ -1,5 +1,12 @@
|
|
|
|
use zingprocmacros::make_instruction_type;
|
|
|
|
use zingprocmacros::make_instruction_type;
|
|
|
|
use crate::encoding::text::ZsciiString;
|
|
|
|
use crate::{encoding::text::{decode_zchars, ZsciiString}, utils::simplecursor::SimpleCursor};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trait Opcode: Sized {
|
|
|
|
|
|
|
|
fn has_store(inst_no: u8) -> bool;
|
|
|
|
|
|
|
|
fn has_branch(inst_no: u8) -> bool;
|
|
|
|
|
|
|
|
fn has_text(inst_no: u8) -> bool;
|
|
|
|
|
|
|
|
fn from_number(inst_no: u8, store: Option<u8>, branch: Option<Branch>, text: Option<ZsciiString>) -> Result<Self, InstructionDecodeError>;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub enum Instruction {
|
|
|
|
pub enum Instruction {
|
|
|
|
Short0Op {
|
|
|
|
Short0Op {
|
|
|
@ -27,17 +34,29 @@ pub enum Instruction {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub enum VariableOperands {
|
|
|
|
|
|
|
|
TwoOp(Operand, Operand),
|
|
|
|
|
|
|
|
VarOp(Vec<Operand>),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub enum Operand {
|
|
|
|
pub enum Operand {
|
|
|
|
SmallConstant(u8),
|
|
|
|
SmallConstant(u8),
|
|
|
|
LargeConstant(u16),
|
|
|
|
LargeConstant(u16),
|
|
|
|
Variable(u8),
|
|
|
|
Variable(u8),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Operand {
|
|
|
|
|
|
|
|
fn make_small(input: &[u8; 1]) -> Self {Self::SmallConstant(input[0])}
|
|
|
|
|
|
|
|
fn make_large(input: &[u8; 2]) -> Self {Self::LargeConstant(u16::from_be_bytes(*input))}
|
|
|
|
|
|
|
|
fn make_var(input: &[u8; 1]) -> Self {Self::Variable(input[0])}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum BranchTarget {
|
|
|
|
|
|
|
|
ReturnsFalse,
|
|
|
|
|
|
|
|
ReturnsTrue,
|
|
|
|
|
|
|
|
ShiftedOffset(i16),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct Branch {
|
|
|
|
|
|
|
|
branch_if_true: bool,
|
|
|
|
|
|
|
|
target: BranchTarget
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
make_instruction_type!(Short0OpOpcode {
|
|
|
|
make_instruction_type!(Short0OpOpcode {
|
|
|
|
0x0 Rtrue,
|
|
|
|
0x0 Rtrue,
|
|
|
|
0x1 Rfalse,
|
|
|
|
0x1 Rfalse,
|
|
|
@ -72,7 +91,7 @@ make_instruction_type!(Short1OpOpcode {
|
|
|
|
0xF Call1n,
|
|
|
|
0xF Call1n,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
make_instruction_type!(Long2OpOpcode {
|
|
|
|
make_instruction_type!(Variable2OpOpcode {
|
|
|
|
1 Je br,
|
|
|
|
1 Je br,
|
|
|
|
2 Jl br,
|
|
|
|
2 Jl br,
|
|
|
|
3 Jg br,
|
|
|
|
3 Jg br,
|
|
|
@ -103,7 +122,7 @@ make_instruction_type!(Long2OpOpcode {
|
|
|
|
28 Throw,
|
|
|
|
28 Throw,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
type Variable2OpOpcode = Long2OpOpcode;
|
|
|
|
type Long2OpOpcode = Variable2OpOpcode;
|
|
|
|
|
|
|
|
|
|
|
|
make_instruction_type!(VariableVarOpOpcode {
|
|
|
|
make_instruction_type!(VariableVarOpOpcode {
|
|
|
|
0 CallVs,
|
|
|
|
0 CallVs,
|
|
|
@ -155,50 +174,190 @@ make_instruction_type!(ExtendedVarOpOpcode {
|
|
|
|
|
|
|
|
|
|
|
|
pub enum InstructionDecodeError {
|
|
|
|
pub enum InstructionDecodeError {
|
|
|
|
InvalidOpcodeNum(u8),
|
|
|
|
InvalidOpcodeNum(u8),
|
|
|
|
|
|
|
|
/// Should not occur unless there is an error in Zing
|
|
|
|
|
|
|
|
MissingStore,
|
|
|
|
|
|
|
|
/// Should not occur unless there is an error in Zing
|
|
|
|
|
|
|
|
MissingBranch,
|
|
|
|
|
|
|
|
/// Should not occur unless there is an error in Zing
|
|
|
|
|
|
|
|
MissingText,
|
|
|
|
|
|
|
|
InsufficientOperands,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns: Some((instruction, instruction_length)) if instruction is valid, None otherwise
|
|
|
|
pub fn decode_instruction(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
|
|
|
|
pub fn decode_instruction(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
|
|
|
struct HasStoreBranchText { store: bool, branch: bool, text: bool }
|
|
|
|
// TODO
|
|
|
|
struct PotentialStoreBranchText {
|
|
|
|
fn decode_short(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
|
|
|
store: Option<u8>,
|
|
|
|
let mut cursor = address;
|
|
|
|
branch: Option<Branch>,
|
|
|
|
let base = memory[cursor];
|
|
|
|
text: Option<ZsciiString>,
|
|
|
|
cursor += 1;
|
|
|
|
}
|
|
|
|
let opcode_num = base & 0x0f;
|
|
|
|
|
|
|
|
let has_operand = base & 0x30 != 0x30;
|
|
|
|
fn decode_store_branch_text<T: Opcode>(
|
|
|
|
if has_operand {
|
|
|
|
cursor: &mut SimpleCursor,
|
|
|
|
todo!("get operand and define store/branch/text if necessary");
|
|
|
|
opcode_num: u8
|
|
|
|
// let instruction = Short1OpOpcode::from_number(opcode_num, store, branch, text).ok_or(InstructionDecodeError::InvalidOpcodeNum(opcode_num))?;
|
|
|
|
) -> PotentialStoreBranchText {
|
|
|
|
todo!()
|
|
|
|
let required = HasStoreBranchText {
|
|
|
|
|
|
|
|
store: T::has_store(opcode_num),
|
|
|
|
|
|
|
|
branch: T::has_branch(opcode_num),
|
|
|
|
|
|
|
|
text: T::has_text(opcode_num)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let store = required.store.then(|| cursor.read_const::<1>().unwrap()[0]);
|
|
|
|
|
|
|
|
let branch = required.branch.then(|| {
|
|
|
|
|
|
|
|
let &[branch_first_byte] = cursor.read_const::<1>().unwrap();
|
|
|
|
|
|
|
|
let branch_if_true = branch_first_byte & 0x80 != 0;
|
|
|
|
|
|
|
|
let branch_target_raw_high = branch_first_byte & 0x3f;
|
|
|
|
|
|
|
|
let branch_target_raw = if branch_first_byte & 0x40 == 0 {
|
|
|
|
|
|
|
|
// one byte
|
|
|
|
|
|
|
|
branch_target_raw_high as i16
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
todo!()
|
|
|
|
// two bytes
|
|
|
|
|
|
|
|
let &[branch_target_raw_low] = cursor.read_const::<1>().unwrap();
|
|
|
|
|
|
|
|
let branch_target_raw_unsigned = i16::from_be_bytes([branch_target_raw_high, branch_target_raw_low]);
|
|
|
|
|
|
|
|
(branch_target_raw_unsigned << 2) / 4
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
match branch_target_raw {
|
|
|
|
|
|
|
|
0 => Branch { branch_if_true, target: BranchTarget::ReturnsFalse },
|
|
|
|
|
|
|
|
1 => Branch { branch_if_true, target: BranchTarget::ReturnsTrue },
|
|
|
|
|
|
|
|
n => Branch { branch_if_true, target: BranchTarget::ShiftedOffset(n - 2) },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: switch away from cursor.buf(); should not assume that the cursor is looking at the
|
|
|
|
|
|
|
|
// entire slice
|
|
|
|
|
|
|
|
let extra_cursor = cursor.clone();
|
|
|
|
|
|
|
|
let mem = extra_cursor.buf();
|
|
|
|
|
|
|
|
let alphabet_addr = u16::from_be_bytes([mem[0x08], mem[0x09]]) as usize;
|
|
|
|
|
|
|
|
let abbreviations_addr = u16::from_be_bytes([mem[0x18], mem[0x18]]) as usize;
|
|
|
|
|
|
|
|
let text = required.branch.then(|| decode_zchars(cursor, alphabet_addr, abbreviations_addr, mem).unwrap());
|
|
|
|
|
|
|
|
PotentialStoreBranchText { store, branch, text }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
fn decode_short(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
|
|
|
|
fn decode_long(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
|
|
|
let &[base] = cursor.read_const::<1>().unwrap();
|
|
|
|
todo!()
|
|
|
|
let opcode_num = base & 0x0f;
|
|
|
|
|
|
|
|
let operand_desc = (base >> 4) & 0b11;
|
|
|
|
|
|
|
|
let operand = match operand_desc {
|
|
|
|
|
|
|
|
0b00 => Some(Operand::make_large(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b01 => Some(Operand::make_small(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b10 => Some(Operand::make_var(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b11 => None,
|
|
|
|
|
|
|
|
_ => unreachable!()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let PotentialStoreBranchText { store, branch, text } = if operand.is_none() {
|
|
|
|
|
|
|
|
decode_store_branch_text::<Short0OpOpcode>(cursor, opcode_num)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
decode_store_branch_text::<Short1OpOpcode>(cursor, opcode_num)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(operand) = operand {
|
|
|
|
|
|
|
|
Short1OpOpcode::from_number(opcode_num, store, branch, text)
|
|
|
|
|
|
|
|
.map(|opcode| Instruction::Short1Op { operand, opcode })
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
Short0OpOpcode::from_number(opcode_num, store, branch, text)
|
|
|
|
|
|
|
|
.map(|opcode| Instruction::Short0Op { opcode })
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
fn decode_long(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
|
|
|
|
fn decode_extended(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
|
|
|
enum OperandType { SmallConst, Var }
|
|
|
|
todo!()
|
|
|
|
impl From<bool> for OperandType {
|
|
|
|
|
|
|
|
fn from(b: bool) -> Self {if b {Self::Var} else {Self::SmallConst}}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
let &[base] = cursor.read_const().unwrap();
|
|
|
|
|
|
|
|
let opcode_num = base & 0x1f;
|
|
|
|
|
|
|
|
let op1_type = OperandType::from(base & 0x40 != 0);
|
|
|
|
|
|
|
|
let op2_type = OperandType::from(base & 0x20 != 0);
|
|
|
|
|
|
|
|
let operand_1 = match op1_type {
|
|
|
|
|
|
|
|
OperandType::SmallConst => Operand::make_small(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
OperandType::Var => Operand::make_var(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let operand_2 = match op2_type {
|
|
|
|
|
|
|
|
OperandType::SmallConst => Operand::make_small(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
OperandType::Var => Operand::make_var(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<Long2OpOpcode>(cursor, opcode_num);
|
|
|
|
|
|
|
|
Long2OpOpcode::from_number(opcode_num, store, branch,text)
|
|
|
|
|
|
|
|
.map(|opcode| Instruction::Long2Op { operands: (operand_1, operand_2), opcode })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
fn decode_extended(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
|
|
|
|
fn decode_variable(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
|
|
|
|
let _ = cursor.read_const::<1>();
|
|
|
|
todo!()
|
|
|
|
let &[opcode_num] = cursor.read_const().unwrap();
|
|
|
|
|
|
|
|
let &[op_descs] = cursor.read_const().unwrap();
|
|
|
|
|
|
|
|
let operands = {
|
|
|
|
|
|
|
|
let mut operands = vec![];
|
|
|
|
|
|
|
|
for i in 0..4 {
|
|
|
|
|
|
|
|
match (op_descs >> (6 - 2 * i)) & 0b11 {
|
|
|
|
|
|
|
|
0b00 => operands.push(Operand::make_large(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b01 => operands.push(Operand::make_small(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b10 => operands.push(Operand::make_var(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b11 => (),
|
|
|
|
|
|
|
|
_ => unreachable!()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
operands
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<ExtendedVarOpOpcode>(cursor, opcode_num);
|
|
|
|
|
|
|
|
ExtendedVarOpOpcode::from_number(opcode_num, store, branch, text)
|
|
|
|
|
|
|
|
.map(|opcode| Instruction::ExtendedVarOp { operands, opcode })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if memory[address] == 190 {
|
|
|
|
fn decode_variable(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
|
|
|
|
decode_extended(memory, address)
|
|
|
|
const CALL_VS2_OPCODE_NUM: u8 = 12;
|
|
|
|
} else if memory[address] & 0xC0 == 0xC0 {
|
|
|
|
const CALL_VN2_OPCODE_NUM: u8 = 26;
|
|
|
|
decode_variable(memory, address)
|
|
|
|
let &[base] = cursor.read_const().unwrap();
|
|
|
|
} else if memory[address] & 0xC0 == 0x80 {
|
|
|
|
let opcode_num = base & 0x1f;
|
|
|
|
decode_short(memory, address)
|
|
|
|
let is_varop = base & 0x20 != 0;
|
|
|
|
|
|
|
|
if is_varop {
|
|
|
|
|
|
|
|
let mut operands = vec![];
|
|
|
|
|
|
|
|
let num_operand_desc_bytes =
|
|
|
|
|
|
|
|
if opcode_num == CALL_VN2_OPCODE_NUM
|
|
|
|
|
|
|
|
|| opcode_num == CALL_VS2_OPCODE_NUM
|
|
|
|
|
|
|
|
{2} else {1};
|
|
|
|
|
|
|
|
for _ in 0..num_operand_desc_bytes {
|
|
|
|
|
|
|
|
let &[op_descs] = cursor.read_const().unwrap();
|
|
|
|
|
|
|
|
for i in 0..4 {
|
|
|
|
|
|
|
|
match (op_descs >> (6 - 2 * i)) & 0b11 {
|
|
|
|
|
|
|
|
0b00 => operands.push(Operand::make_large(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b01 => operands.push(Operand::make_small(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b10 => operands.push(Operand::make_var(cursor.read_const().unwrap())),
|
|
|
|
|
|
|
|
0b11 => (),
|
|
|
|
|
|
|
|
_ => unreachable!()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<VariableVarOpOpcode>(cursor, opcode_num);
|
|
|
|
|
|
|
|
VariableVarOpOpcode::from_number(opcode_num, store, branch, text)
|
|
|
|
|
|
|
|
.map(|opcode| Instruction::VariableVarOp { operands, opcode })
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
decode_long(memory, address)
|
|
|
|
let &[op_descs] = cursor.read_const().unwrap();
|
|
|
|
|
|
|
|
let op0 = match (op_descs >> 6) & 0b11 {
|
|
|
|
|
|
|
|
0b00 => Operand::make_large(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
0b01 => Operand::make_small(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
0b10 => Operand::make_var(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
0b11 => return Err(InstructionDecodeError::InsufficientOperands),
|
|
|
|
|
|
|
|
_ => unreachable!()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let op1 = match (op_descs >> 4) & 0b11 {
|
|
|
|
|
|
|
|
0b00 => Operand::make_large(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
0b01 => Operand::make_small(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
0b10 => Operand::make_var(cursor.read_const().unwrap()),
|
|
|
|
|
|
|
|
0b11 => return Err(InstructionDecodeError::InsufficientOperands),
|
|
|
|
|
|
|
|
_ => unreachable!()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let operands = (op0, op1);
|
|
|
|
|
|
|
|
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<Variable2OpOpcode>(cursor, opcode_num);
|
|
|
|
|
|
|
|
Variable2OpOpcode::from_number(opcode_num, store, branch, text)
|
|
|
|
|
|
|
|
.map(|opcode| Instruction::Variable2Op { operands, opcode })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let &[first_byte] = cursor.peek_const::<1>().unwrap();
|
|
|
|
|
|
|
|
if first_byte == 190 {
|
|
|
|
|
|
|
|
decode_extended(cursor)
|
|
|
|
|
|
|
|
} else if first_byte & 0xC0 == 0xC0 {
|
|
|
|
|
|
|
|
decode_variable(cursor)
|
|
|
|
|
|
|
|
} else if first_byte & 0xC0 == 0x80 {
|
|
|
|
|
|
|
|
decode_short(cursor)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
decode_long(cursor)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|