diff --git a/zing/src/encoding/instruction.rs b/zing/src/encoding/instruction.rs index eb060d6..88516fc 100644 --- a/zing/src/encoding/instruction.rs +++ b/zing/src/encoding/instruction.rs @@ -1,5 +1,12 @@ 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, branch: Option, text: Option) -> Result; +} pub enum Instruction { Short0Op { @@ -27,17 +34,29 @@ pub enum Instruction { }, } -pub enum VariableOperands { - TwoOp(Operand, Operand), - VarOp(Vec), -} - pub enum Operand { SmallConstant(u8), LargeConstant(u16), 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 { 0x0 Rtrue, 0x1 Rfalse, @@ -72,7 +91,7 @@ make_instruction_type!(Short1OpOpcode { 0xF Call1n, }); -make_instruction_type!(Long2OpOpcode { +make_instruction_type!(Variable2OpOpcode { 1 Je br, 2 Jl br, 3 Jg br, @@ -103,7 +122,7 @@ make_instruction_type!(Long2OpOpcode { 28 Throw, }); -type Variable2OpOpcode = Long2OpOpcode; +type Long2OpOpcode = Variable2OpOpcode; make_instruction_type!(VariableVarOpOpcode { 0 CallVs, @@ -155,50 +174,190 @@ make_instruction_type!(ExtendedVarOpOpcode { pub enum InstructionDecodeError { 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(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> { - // TODO - fn decode_short(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> { - let mut cursor = address; - let base = memory[cursor]; - cursor += 1; +pub fn decode_instruction(cursor: &mut SimpleCursor) -> Result { + struct HasStoreBranchText { store: bool, branch: bool, text: bool } + struct PotentialStoreBranchText { + store: Option, + branch: Option, + text: Option, + } + + fn decode_store_branch_text( + cursor: &mut SimpleCursor, + opcode_num: u8 + ) -> PotentialStoreBranchText { + 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 { + // 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 } + } + + fn decode_short(cursor: &mut SimpleCursor) -> Result { + let &[base] = cursor.read_const::<1>().unwrap(); let opcode_num = base & 0x0f; - let has_operand = base & 0x30 != 0x30; - if has_operand { - todo!("get operand and define store/branch/text if necessary"); - // let instruction = Short1OpOpcode::from_number(opcode_num, store, branch, text).ok_or(InstructionDecodeError::InvalidOpcodeNum(opcode_num))?; - todo!() + 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::(cursor, opcode_num) + } else { + decode_store_branch_text::(cursor, opcode_num) + }; + + if let Some(operand) = operand { + Short1OpOpcode::from_number(opcode_num, store, branch, text) + .map(|opcode| Instruction::Short1Op { operand, opcode }) } else { - todo!() + Short0OpOpcode::from_number(opcode_num, store, branch, text) + .map(|opcode| Instruction::Short0Op { opcode }) } } - // TODO - fn decode_long(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> { - todo!() + fn decode_long(cursor: &mut SimpleCursor) -> Result { + enum OperandType { SmallConst, Var } + impl From 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::(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(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> { - todo!() + fn decode_extended(cursor: &mut SimpleCursor) -> Result { + let _ = cursor.read_const::<1>(); + 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::(cursor, opcode_num); + ExtendedVarOpOpcode::from_number(opcode_num, store, branch, text) + .map(|opcode| Instruction::ExtendedVarOp { operands, opcode }) } - // TODO - fn decode_variable(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> { - todo!() + fn decode_variable(cursor: &mut SimpleCursor) -> Result { + const CALL_VS2_OPCODE_NUM: u8 = 12; + const CALL_VN2_OPCODE_NUM: u8 = 26; + let &[base] = cursor.read_const().unwrap(); + let opcode_num = base & 0x1f; + 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::(cursor, opcode_num); + VariableVarOpOpcode::from_number(opcode_num, store, branch, text) + .map(|opcode| Instruction::VariableVarOp { operands, opcode }) + } else { + 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::(cursor, opcode_num); + Variable2OpOpcode::from_number(opcode_num, store, branch, text) + .map(|opcode| Instruction::Variable2Op { operands, opcode }) + } } - if memory[address] == 190 { - decode_extended(memory, address) - } else if memory[address] & 0xC0 == 0xC0 { - decode_variable(memory, address) - } else if memory[address] & 0xC0 == 0x80 { - decode_short(memory, address) + 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(memory, address) + decode_long(cursor) } } - - diff --git a/zing/src/encoding/text.rs b/zing/src/encoding/text.rs index 1196e67..608f491 100644 --- a/zing/src/encoding/text.rs +++ b/zing/src/encoding/text.rs @@ -20,7 +20,7 @@ fn zscii_from_bytes(bytes: &[u8]) -> ZsciiString { pub fn decode_zchars( zchars: &mut SimpleCursor, alphabet_table_addr: usize, - abbreviations_table: usize, + abbreviations_table_addr: usize, memory: &[u8], ) -> Option { fn get_from_alphabet(alphabet_number: usize, ZChar(codepoint): ZChar, alphabet_table: Option<&[u8]>) -> ZsciiChar { diff --git a/zing/src/main.rs b/zing/src/main.rs index 98ef43c..46ec48c 100644 --- a/zing/src/main.rs +++ b/zing/src/main.rs @@ -2,6 +2,7 @@ mod encoding; mod utils; +mod state; use std::fs::File; diff --git a/zing/src/state.rs b/zing/src/state.rs new file mode 100644 index 0000000..370e2aa --- /dev/null +++ b/zing/src/state.rs @@ -0,0 +1,27 @@ +pub struct Z5 { + memory: [u8; 128 * 1024], + pc: usize, + call_stack: Vec, +} + +pub enum ReturnTarget { + ReturnIllegal, + Discard, + /// note that writing to a local variable targets the local on the stack frame + /// being returned *to*, not the stack frame being returned *from*, as the latter + /// is discarded during the return + WriteVariable(u8), +} + +pub struct StackFrame { + stack: Vec, + // length must be no greater than 15 + locals: Vec, + return_address: usize, +} + +impl Z5 { + pub fn run_instruction(&mut self) { + todo!() + } +} diff --git a/zing/src/utils/simplecursor.rs b/zing/src/utils/simplecursor.rs index 083d863..1a4e44d 100644 --- a/zing/src/utils/simplecursor.rs +++ b/zing/src/utils/simplecursor.rs @@ -1,5 +1,3 @@ -use std::ops::Sub; - #[derive(Clone)] pub struct SimpleCursor<'a> { buffer: &'a [u8], @@ -11,6 +9,10 @@ impl<'a> SimpleCursor<'a> { Self { buffer, index: 0 } } + pub fn new_with_index(buffer: &'a [u8], index: usize) -> Self { + Self { buffer, index } + } + pub fn buffer_length(&self) -> usize { self.buffer.len() } fn remaining_length(&self) -> usize { @@ -88,7 +90,7 @@ impl<'a> SimpleCursor<'a> { pub fn buf(&self) -> &[u8] { self.buffer } - pub fn idx(&self) -> usize { self.index } + pub fn index(&self) -> usize { self.index } } impl<'a> Iterator for SimpleCursor<'a> { diff --git a/zingprocmacros/src/lib.rs b/zingprocmacros/src/lib.rs index 1577093..e77da85 100644 --- a/zingprocmacros/src/lib.rs +++ b/zingprocmacros/src/lib.rs @@ -69,7 +69,7 @@ pub fn make_instruction_type(tokens: proc_macro::TokenStream) -> proc_macro::Tok quote!{#name} } else { let store = store.map(|_| quote! { store: u8, }); - let branch = branch.map(|_| quote! { branch: u16, }); + let branch = branch.map(|_| quote! { branch: Branch, }); let text = text.map(|_| quote! { text: ZsciiString, }); quote! { #name { #store #branch #text } } } @@ -94,44 +94,44 @@ pub fn make_instruction_type(tokens: proc_macro::TokenStream) -> proc_macro::Tok let constructor_function_branches = instruction_type.entries.iter().map(|InstructionEntry { number, name, store, branch, text }| { if store.is_none() && branch.is_none() && text.is_none() { - quote!{ #number => ::std::option::Option::Some(Self::#name) } + quote!{ #number => ::std::result::Result::Ok(Self::#name) } } else { - let store = store.map(|_| quote! { store: store?, }); - let branch = branch.map(|_| quote! { branch: branch?, }); - let text = text.map(|_| quote! { text: text?, }); - quote! { #number => ::std::option::Option::Some(Self::#name {#store #branch #text}) } + let store = store.map(|_| quote! { store: store.ok_or(InstructionDecodeError::MissingStore)?, }); + let branch = branch.map(|_| quote! { branch: branch.ok_or(InstructionDecodeError::MissingBranch)?, }); + let text = text.map(|_| quote! { text: text.ok_or(InstructionDecodeError::MissingText)?, }); + quote! { #number => ::std::result::Result::Ok(Self::#name {#store #branch #text}) } } }); quote! { pub enum #type_name { #(#initial_enum_variants),* } - impl #type_name { - pub fn has_store(inst_no: u8) -> bool { + impl Opcode for #type_name { + fn has_store(inst_no: u8) -> bool { match inst_no { #(#has_store_branches,)* _ => false } } - pub fn has_branch(inst_no: u8) -> bool { + fn has_branch(inst_no: u8) -> bool { match inst_no { #(#has_branch_branches,)* _ => false } } - pub fn has_text(inst_no: u8) -> bool { + fn has_text(inst_no: u8) -> bool { match inst_no { #(#has_text_branches,)* _ => false } } - pub fn from_number(inst_no: u8, store: Option, branch: Option, text: Option) -> Option { + fn from_number(inst_no: u8, store: Option, branch: Option, text: Option) -> Result { match inst_no { #(#constructor_function_branches,)* - _ => None + _ => Err(InstructionDecodeError::InvalidOpcodeNum(inst_no)) } } }