Major refactor: change memory to be based on a view, rather than some slice

This fixes a problem where we couldn't read memory in a way that wraps around, like the gameboy does.
old-bit-manip
Nick Krichevsky 2023-04-30 19:10:32 -04:00
parent 10b9c00f72
commit a8736052d1
12 changed files with 768 additions and 492 deletions

View File

@ -24,14 +24,13 @@ impl Processor {
.memory
.get_from(pc.into())
.expect("program counter out of range");
let (instruction, rest_memory) =
parse::next_instruction(memory_view).expect("invalid instruction");
let (next_pc, _carry) =
pc.overflowing_add(u16::try_from(memory_view.len() - rest_memory.len()).unwrap());
let (instruction, bytes_read) =
parse::next_instruction(&memory_view).expect("invalid instruction");
self.run_instruction(&instruction);
// wow this code sucks; I may change the parse code to return a number of bytes read,
// rather than a memory view.
let (next_pc, _carry) = pc.overflowing_add(bytes_read);
self.registers.program_counter = next_pc;
}

View File

@ -1,6 +1,8 @@
//! The `instructions` module holds structural definitions for all of the instructions that can run on the gameboy.
use super::parse::{self, ParseResult};
use crate::memory::View;
use super::parse::{self, Error};
pub mod arith8;
pub mod load16;
@ -26,9 +28,12 @@ impl RunnableInstruction {
/// `from_data` will produce an instruction from the given data and return the data after
/// processing that operation.
///
/// This is only available for testing in order to get instructions that can be run standalone
///
/// # Errors
/// Returns an error if the instruction couldn't be parsed.
pub fn from_data(data: &[u8]) -> ParseResult {
parse::next_instruction(data)
pub fn from_data(data: &[u8]) -> Result<(RunnableInstruction, &[u8]), Error> {
parse::next_instruction(&View::new_from_data(data, 0))
.map(|(ins, offset)| (ins, &data[offset.into()..]))
}
}

View File

@ -1,5 +1,7 @@
//! The `parse` module holds functions that will parse streams of data into an [`crate::instructions::Instruction`]
use crate::memory::View;
use super::instructions::RunnableInstruction;
use thiserror::Error;
@ -17,20 +19,21 @@ pub enum Error {
NotEnoughArgs(u8),
}
/// `ParseResult` is the result of a Parse operation
pub(super) type ParseResult<'a> = Result<(RunnableInstruction, &'a [u8]), Error>;
/// `ParseResult` is the result of a Parse operation, which includes the parsed instruction and the number of bytes read.
pub(super) type ParseOutput = (RunnableInstruction, u16);
pub(super) type ParseResult = Result<ParseOutput, Error>;
/// `OpcodeParser` takes input data, parses out an opcode (and its associated arguments) if it can, and returns
/// the remaining data after reading it.
trait OpcodeParser {
/// Parse an opcode and all of its data from the given data buffer. Returns either an error, or
/// The parsed instruction, and a slice of all the data after the instruction
fn parse_opcode(data: &[u8]) -> ParseResult;
fn parse_opcode(data: &View) -> ParseResult;
}
/// `next_instruction` will parse the next instruction from the given data stream. Returns either an error,
/// or the parsed instruction, and a slice of all the data after the instruction
pub fn next_instruction(data: &[u8]) -> ParseResult {
pub fn next_instruction(data: &View) -> ParseResult {
let parse_funcs = &[
load8::immediate::Immediate8BitLoadParser::parse_opcode,
load8::transfer::Between8BitRegisterParser::parse_opcode,
@ -49,11 +52,10 @@ pub fn next_instruction(data: &[u8]) -> ParseResult {
}
}
let opcode = get_opcode_from_data(data)?;
let opcode = get_opcode_from_data(data);
Err(Error::UnknownOpcode(opcode))
}
fn get_opcode_from_data(data: &[u8]) -> Result<u8, Error> {
data.first().copied().ok_or(Error::NoData)
fn get_opcode_from_data(data: &View) -> u8 {
data.get()
}

View File

@ -1,116 +1,111 @@
use crate::{
cpu::{
instructions::{arith8::EightBitArithmeticInstruction, Instruction, RunnableInstruction},
parse::{self, Error, OpcodeParser, ParseResult},
parse::{self, Error, OpcodeParser, ParseOutput, ParseResult},
},
memory::{GetViewTuple, View},
register,
};
pub struct EightBitAddParser;
impl OpcodeParser for EightBitAddParser {
fn parse_opcode(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x87 => build_add_register_to_a_data(register::SingleEightBit::A, data),
0x80 => build_add_register_to_a_data(register::SingleEightBit::B, data),
0x81 => build_add_register_to_a_data(register::SingleEightBit::C, data),
0x82 => build_add_register_to_a_data(register::SingleEightBit::D, data),
0x83 => build_add_register_to_a_data(register::SingleEightBit::E, data),
0x84 => build_add_register_to_a_data(register::SingleEightBit::H, data),
0x85 => build_add_register_to_a_data(register::SingleEightBit::L, data),
0x86 => build_add_hl_address_to_a_data(data),
0xC6 => build_add_immediate_to_a_data(data),
0x88 => build_add_register_to_a_with_carry_data(register::SingleEightBit::B, data),
0x89 => build_add_register_to_a_with_carry_data(register::SingleEightBit::C, data),
0x8A => build_add_register_to_a_with_carry_data(register::SingleEightBit::D, data),
0x8B => build_add_register_to_a_with_carry_data(register::SingleEightBit::E, data),
0x8C => build_add_register_to_a_with_carry_data(register::SingleEightBit::H, data),
0x8D => build_add_register_to_a_with_carry_data(register::SingleEightBit::L, data),
0xCE => build_add_immediate_to_a_with_carry_data(data),
0x87 => Ok(build_add_register_to_a_data(register::SingleEightBit::A)),
0x80 => Ok(build_add_register_to_a_data(register::SingleEightBit::B)),
0x81 => Ok(build_add_register_to_a_data(register::SingleEightBit::C)),
0x82 => Ok(build_add_register_to_a_data(register::SingleEightBit::D)),
0x83 => Ok(build_add_register_to_a_data(register::SingleEightBit::E)),
0x84 => Ok(build_add_register_to_a_data(register::SingleEightBit::H)),
0x85 => Ok(build_add_register_to_a_data(register::SingleEightBit::L)),
0x86 => Ok(build_add_hl_address_to_a_data()),
0xC6 => Ok(build_add_immediate_to_a_data(data)),
0x88 => Ok(build_add_register_to_a_with_carry_data(
register::SingleEightBit::B,
)),
0x89 => Ok(build_add_register_to_a_with_carry_data(
register::SingleEightBit::C,
)),
0x8A => Ok(build_add_register_to_a_with_carry_data(
register::SingleEightBit::D,
)),
0x8B => Ok(build_add_register_to_a_with_carry_data(
register::SingleEightBit::E,
)),
0x8C => Ok(build_add_register_to_a_with_carry_data(
register::SingleEightBit::H,
)),
0x8D => Ok(build_add_register_to_a_with_carry_data(
register::SingleEightBit::L,
)),
0xCE => Ok(build_add_immediate_to_a_with_carry_data(data)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
}
fn build_add_register_to_a_data(src: register::SingleEightBit, data: &[u8]) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddSingleRegisterToA { src },
),
cycles: 4,
},
remaining_data,
)
})
.ok_or(Error::NoData)
fn build_add_register_to_a_data(src: register::SingleEightBit) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddSingleRegisterToA { src },
),
cycles: 4,
},
1,
)
}
fn build_add_register_to_a_with_carry_data(
src: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src },
),
cycles: 4,
},
remaining_data,
)
})
.ok_or(Error::NoData)
fn build_add_register_to_a_with_carry_data(src: register::SingleEightBit) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src },
),
cycles: 4,
},
1,
)
}
fn build_add_hl_address_to_a_data(data: &[u8]) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddHLAddressToA,
),
cycles: 8,
},
remaining_data,
)
})
.ok_or(Error::NoData)
fn build_add_hl_address_to_a_data() -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddHLAddressToA,
),
cycles: 8,
},
1,
)
}
fn build_add_immediate_to_a_data(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let n = data.get(1).copied().ok_or(Error::NotEnoughArgs(opcode))?;
fn build_add_immediate_to_a_data(data: &View) -> ParseOutput {
let (_opcode, n) = data.get_tuple();
Ok((
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddImmediateToA { n },
),
cycles: 8,
},
// Guaranteed to exist because the above succeeded
&data[2..],
))
2,
)
}
fn build_add_immediate_to_a_with_carry_data(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let n = data.get(1).copied().ok_or(Error::NotEnoughArgs(opcode))?;
fn build_add_immediate_to_a_with_carry_data(data: &View) -> ParseOutput {
let (_opcode, n) = data.get_tuple();
Ok((
(
RunnableInstruction {
instruction: Instruction::EightBitArithmetic(
EightBitArithmeticInstruction::AddImmediateToAWithCarry { n },
),
cycles: 8,
},
&data[2..],
))
2,
)
}

View File

@ -1,64 +1,63 @@
use crate::register;
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
use crate::cpu::parse::ParseOutput;
use crate::cpu::{
instructions::{Instruction, RunnableInstruction},
parse::{self, Error, OpcodeParser, ParseResult},
};
use crate::memory::{GetViewTuple, View};
use crate::register;
#[allow(clippy::module_name_repetitions)]
pub struct Immediate16BitLoadParser;
impl OpcodeParser for Immediate16BitLoadParser {
fn parse_opcode(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x01 => make_load_immediate_data(
0x01 => Ok(make_load_immediate_data(
register::SixteenBit::Combined(register::Combined::BC),
data,
),
0x11 => make_load_immediate_data(
)),
0x11 => Ok(make_load_immediate_data(
register::SixteenBit::Combined(register::Combined::DE),
data,
),
0x21 => make_load_immediate_data(
)),
0x21 => Ok(make_load_immediate_data(
register::SixteenBit::Combined(register::Combined::HL),
data,
),
0x31 => make_load_immediate_data(
)),
0x31 => Ok(make_load_immediate_data(
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
data,
),
0xF8 => make_load_effective_address(register::Combined::HL, data),
)),
0xF8 => Ok(make_load_effective_address(register::Combined::HL, data)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
}
fn make_load_immediate_data(dst: register::SixteenBit, data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let args = data.get(1..=2).ok_or(Error::NotEnoughArgs(opcode))?;
fn make_load_immediate_data(dst: register::SixteenBit, data: &View) -> ParseOutput {
let (_opcode, lower_bytes, upper_bytes) = data.get_tuple();
// manual doesn't state this should be LE, but some inspection of games and googling
// indicates it should be.
let value = u16::from_le_bytes([args[0], args[1]]);
Ok((
let value = u16::from_le_bytes([lower_bytes, upper_bytes]);
(
RunnableInstruction {
instruction: Instruction::SixteenBitLoad(
SixteenBitLoadInstruction::LoadImmediateToRegister { value, dst },
),
cycles: 12,
},
// guaranteed to succeed given we already took the first two elements as args
&data[3..],
))
3,
)
}
fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let unsigned_value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
fn make_load_effective_address(dst: register::Combined, data: &View) -> ParseOutput {
let (_opcode, unsigned_value) = data.get_tuple();
let signed_value = {
match i8::try_from(*unsigned_value) {
match i8::try_from(unsigned_value) {
Ok(value) => value,
// Convert this from a two's complement unsigned to a signed
Err(_err) => {
@ -72,7 +71,7 @@ fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseRes
}
};
Ok((
(
RunnableInstruction {
instruction: Instruction::SixteenBitLoad(
SixteenBitLoadInstruction::LoadEffectiveAddress {
@ -82,6 +81,6 @@ fn make_load_effective_address(dst: register::Combined, data: &[u8]) -> ParseRes
),
cycles: 12,
},
&data[2..],
))
2,
)
}

View File

@ -1,7 +1,8 @@
use crate::register;
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
use crate::cpu::instructions::{Instruction, RunnableInstruction};
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult};
use crate::memory::View;
use crate::register;
#[allow(clippy::module_name_repetitions)]
pub struct StackLoadParser;
@ -13,47 +14,59 @@ enum Operation {
}
impl OpcodeParser for StackLoadParser {
fn parse_opcode(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0xF5 => make_stack_operation_data(Operation::Push, register::Combined::AF, data),
0xC5 => make_stack_operation_data(Operation::Push, register::Combined::BC, data),
0xD5 => make_stack_operation_data(Operation::Push, register::Combined::DE, data),
0xE5 => make_stack_operation_data(Operation::Push, register::Combined::HL, data),
0xF5 => Ok(make_stack_operation_data(
Operation::Push,
register::Combined::AF,
)),
0xC5 => Ok(make_stack_operation_data(
Operation::Push,
register::Combined::BC,
)),
0xD5 => Ok(make_stack_operation_data(
Operation::Push,
register::Combined::DE,
)),
0xE5 => Ok(make_stack_operation_data(
Operation::Push,
register::Combined::HL,
)),
0xF1 => make_stack_operation_data(Operation::Pop, register::Combined::AF, data),
0xC1 => make_stack_operation_data(Operation::Pop, register::Combined::BC, data),
0xD1 => make_stack_operation_data(Operation::Pop, register::Combined::DE, data),
0xE1 => make_stack_operation_data(Operation::Pop, register::Combined::HL, data),
0xF1 => Ok(make_stack_operation_data(
Operation::Pop,
register::Combined::AF,
)),
0xC1 => Ok(make_stack_operation_data(
Operation::Pop,
register::Combined::BC,
)),
0xD1 => Ok(make_stack_operation_data(
Operation::Pop,
register::Combined::DE,
)),
0xE1 => Ok(make_stack_operation_data(
Operation::Pop,
register::Combined::HL,
)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
}
fn make_stack_operation_data(
operation: Operation,
reg: register::Combined,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
let instruction = match operation {
Operation::Push => RunnableInstruction {
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Push {
src: reg,
}),
cycles: 16,
},
Operation::Pop => RunnableInstruction {
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Pop {
dst: reg,
}),
cycles: 12,
},
};
fn make_stack_operation_data(operation: Operation, reg: register::Combined) -> ParseOutput {
let instruction = match operation {
Operation::Push => RunnableInstruction {
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Push { src: reg }),
cycles: 16,
},
Operation::Pop => RunnableInstruction {
instruction: Instruction::SixteenBitLoad(SixteenBitLoadInstruction::Pop { dst: reg }),
cycles: 12,
},
};
(instruction, remaining_data)
})
.ok_or(Error::NoData)
(instruction, 1)
}

View File

@ -1,23 +1,24 @@
use crate::register;
use crate::cpu::instructions::load16::SixteenBitLoadInstruction;
use crate::cpu::instructions::Instruction;
use crate::cpu::parse::ParseOutput;
use crate::cpu::{
instructions::RunnableInstruction,
parse::{self, Error, OpcodeParser, ParseResult},
};
use crate::memory::View;
use crate::register;
pub struct Between16BitRegisterParser;
impl OpcodeParser for Between16BitRegisterParser {
/// Parses an opcode that transfers an 8bit values between single 8 bit registers.
fn parse_opcode(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
/// Parses an opcode that transfers an 16bit values between single 16 bit registers.
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0xF9 => make_load_between_register_data(
0xF9 => Ok(make_load_between_register_data(
register::SixteenBit::Single(register::SingleSixteenBit::StackPointer),
register::SixteenBit::Combined(register::Combined::HL),
data,
),
)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
@ -26,19 +27,14 @@ impl OpcodeParser for Between16BitRegisterParser {
fn make_load_between_register_data(
dst: register::SixteenBit,
src: register::SixteenBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::SixteenBitLoad(
SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src },
),
cycles: 4,
},
remaining_data,
)
})
.ok_or(Error::NoData)
) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::SixteenBitLoad(
SixteenBitLoadInstruction::LoadBetweenRegisters { dst, src },
),
cycles: 4,
},
1,
)
}

View File

@ -1,44 +1,42 @@
use crate::register;
use crate::cpu::instructions::load8::EightBitLoadInstruction;
use crate::cpu::instructions::Instruction;
use crate::cpu::parse::ParseOutput;
use crate::cpu::{
instructions::RunnableInstruction,
parse::{self, Error, OpcodeParser, ParseResult},
};
use crate::memory::{GetViewTuple, View};
use crate::register;
#[allow(clippy::module_name_repetitions)]
pub struct Immediate8BitLoadParser;
impl OpcodeParser for Immediate8BitLoadParser {
/// Parses an opcode that will transfer an immediate 8 bit value into a single 8 bit register.
fn parse_opcode(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_opcode(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x3E => make_load_immediate_data(register::SingleEightBit::A, data),
0x06 => make_load_immediate_data(register::SingleEightBit::B, data),
0x0E => make_load_immediate_data(register::SingleEightBit::C, data),
0x16 => make_load_immediate_data(register::SingleEightBit::D, data),
0x1E => make_load_immediate_data(register::SingleEightBit::E, data),
0x26 => make_load_immediate_data(register::SingleEightBit::H, data),
0x2E => make_load_immediate_data(register::SingleEightBit::L, data),
0x3E => Ok(make_load_immediate_data(register::SingleEightBit::A, data)),
0x06 => Ok(make_load_immediate_data(register::SingleEightBit::B, data)),
0x0E => Ok(make_load_immediate_data(register::SingleEightBit::C, data)),
0x16 => Ok(make_load_immediate_data(register::SingleEightBit::D, data)),
0x1E => Ok(make_load_immediate_data(register::SingleEightBit::E, data)),
0x26 => Ok(make_load_immediate_data(register::SingleEightBit::H, data)),
0x2E => Ok(make_load_immediate_data(register::SingleEightBit::L, data)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
}
fn make_load_immediate_data(register: register::SingleEightBit, data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
fn make_load_immediate_data(register: register::SingleEightBit, data: &View) -> ParseOutput {
let (_opcode, value) = data.get_tuple();
Ok((
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadImmediateToRegister {
register,
value: *value,
},
EightBitLoadInstruction::LoadImmediateToRegister { register, value },
),
// TODO: I don't love that the cycles are in a parsing function,
// but ultimately I want the cycles to be as close to the opcode as possible
@ -46,7 +44,6 @@ fn make_load_immediate_data(register: register::SingleEightBit, data: &[u8]) ->
// more of the instructions complete
cycles: 8,
},
// This can't fail if the previous .get(1) succeeded
&data[2..],
))
2,
)
}

View File

@ -1,14 +1,15 @@
use crate::register;
use crate::cpu::instructions::load8::EightBitLoadInstruction;
use crate::cpu::instructions::{Instruction, RunnableInstruction};
use crate::cpu::parse::{self, Error, OpcodeParser, ParseResult};
use crate::cpu::parse::{self, Error, OpcodeParser, ParseOutput, ParseResult};
use crate::memory::{GetViewTuple, View};
use crate::register;
#[allow(clippy::module_name_repetitions)]
pub struct Memory8BitLoadParser;
impl OpcodeParser for Memory8BitLoadParser {
// Parses a single 8 bit instruction to load an 8 bit value to/from memory
fn parse_opcode(data: &[u8]) -> ParseResult {
fn parse_opcode(data: &View) -> ParseResult {
let parse_funcs = &[
parse_load_from_register_to_address,
parse_load_from_address_to_register,
@ -24,155 +25,158 @@ impl OpcodeParser for Memory8BitLoadParser {
}
}
let opcode = parse::get_opcode_from_data(data)?;
let opcode = parse::get_opcode_from_data(data);
Err(Error::UnknownOpcode(opcode))
}
}
fn parse_load_from_register_to_address(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_load_from_register_to_address(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x02 => {
make_load_to_register_address(register::Combined::BC, register::SingleEightBit::A, data)
}
0x12 => {
make_load_to_register_address(register::Combined::DE, register::SingleEightBit::A, data)
}
0x70 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::B, data)
}
0x71 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::C, data)
}
0x72 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::D, data)
}
0x73 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::E, data)
}
0x74 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::H, data)
}
0x75 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::L, data)
}
0x77 => {
make_load_to_register_address(register::Combined::HL, register::SingleEightBit::A, data)
}
0x02 => Ok(make_load_to_register_address(
register::Combined::BC,
register::SingleEightBit::A,
)),
0x12 => Ok(make_load_to_register_address(
register::Combined::DE,
register::SingleEightBit::A,
)),
0x70 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::B,
)),
0x71 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::C,
)),
0x72 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::D,
)),
0x73 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::E,
)),
0x74 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::H,
)),
0x75 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::L,
)),
0x77 => Ok(make_load_to_register_address(
register::Combined::HL,
register::SingleEightBit::A,
)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
fn parse_load_from_address_to_register(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_load_from_address_to_register(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x0A => make_load_from_register_address(
0x0A => Ok(make_load_from_register_address(
register::SingleEightBit::A,
register::Combined::BC,
data,
),
0x1A => make_load_from_register_address(
)),
0x1A => Ok(make_load_from_register_address(
register::SingleEightBit::A,
register::Combined::DE,
data,
),
0x46 => make_load_from_register_address(
)),
0x46 => Ok(make_load_from_register_address(
register::SingleEightBit::B,
register::Combined::HL,
data,
),
0x4E => make_load_from_register_address(
)),
0x4E => Ok(make_load_from_register_address(
register::SingleEightBit::C,
register::Combined::HL,
data,
),
0x56 => make_load_from_register_address(
)),
0x56 => Ok(make_load_from_register_address(
register::SingleEightBit::D,
register::Combined::HL,
data,
),
0x5E => make_load_from_register_address(
)),
0x5E => Ok(make_load_from_register_address(
register::SingleEightBit::E,
register::Combined::HL,
data,
),
0x66 => make_load_from_register_address(
)),
0x66 => Ok(make_load_from_register_address(
register::SingleEightBit::H,
register::Combined::HL,
data,
),
0x6E => make_load_from_register_address(
)),
0x6E => Ok(make_load_from_register_address(
register::SingleEightBit::L,
register::Combined::HL,
data,
),
0x7E => make_load_from_register_address(
)),
0x7E => Ok(make_load_from_register_address(
register::SingleEightBit::A,
register::Combined::HL,
data,
),
)),
0xF2 => make_load_from_memory_relative_to_io_register_start(
0xF2 => Ok(make_load_from_memory_relative_to_io_register_start(
register::SingleEightBit::C,
register::SingleEightBit::A,
data,
),
0xE2 => make_load_to_memory_relative_to_io_register_start(
)),
0xE2 => Ok(make_load_to_memory_relative_to_io_register_start(
register::SingleEightBit::C,
register::SingleEightBit::A,
data,
)),
0xF0 => Ok(
make_load_from_memory_relative_to_io_register_start_by_immediate(
register::SingleEightBit::A,
data,
),
),
0xF0 => make_load_from_memory_relative_to_io_register_start_by_immediate(
register::SingleEightBit::A,
data,
),
0xE0 => make_load_to_memory_relative_to_io_register_start_by_immediate(
register::SingleEightBit::A,
data,
0xE0 => Ok(
make_load_to_memory_relative_to_io_register_start_by_immediate(
register::SingleEightBit::A,
data,
),
),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
fn parse_load_immediate_instructions(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_load_immediate_instructions(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x36 => make_load_n_to_hl_address(data),
0xFA => make_load_from_immediate_address(register::SingleEightBit::A, data),
0xEA => make_load_to_immediate_address(register::SingleEightBit::A, data),
0x36 => Ok(make_load_n_to_hl_address(data)),
0xFA => Ok(make_load_from_immediate_address(
register::SingleEightBit::A,
data,
)),
0xEA => Ok(make_load_to_immediate_address(
register::SingleEightBit::A,
data,
)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
fn parse_load_from_register_to_address_and_do_arithmetic(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
fn parse_load_from_register_to_address_and_do_arithmetic(data: &View) -> ParseResult {
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x32 => make_load_to_address_and_do_arithmetic(
0x32 => Ok(make_load_to_address_and_do_arithmetic(
register::Combined::HL,
register::SingleEightBit::A,
|dst, src| EightBitLoadInstruction::LoadToRegisterAddressThenDec { dst, src },
data,
),
0x22 => make_load_to_address_and_do_arithmetic(
)),
0x22 => Ok(make_load_to_address_and_do_arithmetic(
register::Combined::HL,
register::SingleEightBit::A,
|dst, src| EightBitLoadInstruction::LoadToRegisterAddressThenInc { dst, src },
data,
),
0x3A => make_load_from_address_and_do_arithmetic(
)),
0x3A => Ok(make_load_from_address_and_do_arithmetic(
register::SingleEightBit::A,
register::Combined::HL,
|dst, src| EightBitLoadInstruction::LoadFromRegisterAddressThenDec { dst, src },
data,
),
0x2A => make_load_from_address_and_do_arithmetic(
)),
0x2A => Ok(make_load_from_address_and_do_arithmetic(
register::SingleEightBit::A,
register::Combined::HL,
|dst, src| EightBitLoadInstruction::LoadFromRegisterAddressThenInc { dst, src },
data,
),
)),
_ => Err(Error::UnknownOpcode(opcode)),
}
}
@ -180,184 +184,155 @@ fn parse_load_from_register_to_address_and_do_arithmetic(data: &[u8]) -> ParseRe
fn make_load_from_register_address(
dst: register::SingleEightBit,
src: register::Combined,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadFromRegisterAddress { src, dst },
),
cycles: 8,
},
remaining_data,
)
})
.ok_or(Error::NoData)
) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadFromRegisterAddress { src, dst },
),
cycles: 8,
},
1,
)
}
fn make_load_to_register_address(
dst: register::Combined,
src: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadToRegisterAddress { src, dst },
),
cycles: 8,
},
remaining_data,
)
})
.ok_or(Error::NoData)
) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadToRegisterAddress { src, dst },
),
cycles: 8,
},
1,
)
}
fn make_load_from_immediate_address(dst: register::SingleEightBit, data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let args = data.get(1..=2).ok_or(Error::NotEnoughArgs(opcode))?;
fn make_load_from_immediate_address(dst: register::SingleEightBit, data: &View) -> ParseOutput {
let (_opcode, lower_bytes, upper_bytes) = data.get_tuple();
// The manual states that the LSB of the address is specified first (i.e. little endian)
let src_address = u16::from_le_bytes([args[0], args[1]]);
Ok((
let src_address = u16::from_le_bytes([lower_bytes, upper_bytes]);
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadFromImmediateAddress { src_address, dst },
),
cycles: 16,
},
// This can't fail if get(1..=2) passed
&data[3..],
))
3,
)
}
fn make_load_to_immediate_address(src: register::SingleEightBit, data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let args = data.get(1..=2).ok_or(Error::NotEnoughArgs(opcode))?;
fn make_load_to_immediate_address(src: register::SingleEightBit, data: &View) -> ParseOutput {
let (_opcode, lower_bytes, upper_bytes) = data.get_tuple();
// The manual states that the LSB of the address is specified first (i.e. little endian)
let dst_address = u16::from_le_bytes([args[0], args[1]]);
Ok((
let dst_address = u16::from_le_bytes([lower_bytes, upper_bytes]);
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadToImmediateAddress { src, dst_address },
),
cycles: 16,
},
// This can't fail if get(1..=2) passed
&data[3..],
))
3,
)
}
fn make_load_n_to_hl_address(data: &[u8]) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
Ok((
fn make_load_n_to_hl_address(data: &View) -> ParseOutput {
let (_opcode, value) = data.get_tuple();
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(EightBitLoadInstruction::LoadnToHLAddress {
value: *value,
value,
}),
cycles: 12,
},
// This can't fail if get(1) succeeeded
&data[2..],
))
2,
)
}
fn make_load_from_memory_relative_to_io_register_start(
offset_register: register::SingleEightBit,
dst: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart {
offset_register,
dst,
},
),
cycles: 8,
) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStart {
offset_register,
dst,
},
// guaranteed to succeed given we found the opcode
remaining_data,
)
})
.ok_or(Error::NoData)
),
cycles: 8,
},
// guaranteed to succeed given we found the opcode
1,
)
}
fn make_load_to_memory_relative_to_io_register_start(
offset_register: register::SingleEightBit,
src: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart {
src,
offset_register,
},
),
cycles: 8,
) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStart {
src,
offset_register,
},
// guaranteed to succeed given we found the opcode
remaining_data,
)
})
.ok_or(Error::NoData)
),
cycles: 8,
},
// guaranteed to succeed given we found the opcode
1,
)
}
fn make_load_to_memory_relative_to_io_register_start_by_immediate(
src: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
data: &View,
) -> ParseOutput {
let (_opcode, offset) = data.get_tuple();
Ok((
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadToMemoryRelativeToIORegisterStartByImmediate {
src,
offset: *value,
offset,
},
),
// guaranteed to succeed given we got the value
cycles: 12,
},
&data[2..],
))
2,
)
}
fn make_load_from_memory_relative_to_io_register_start_by_immediate(
dst: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
let opcode = parse::get_opcode_from_data(data)?;
let value = data.get(1).ok_or(Error::NotEnoughArgs(opcode))?;
data: &View,
) -> ParseOutput {
let (_opcode, offset) = data.get_tuple();
Ok((
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadFromMemoryRelativeToIORegisterStartByImmediate {
offset: *value,
offset,
dst,
},
),
cycles: 12,
},
// guaranteed to succeed given we got the value
&data[2..],
))
2,
)
}
/// `make_load_to_address_and_do_arithmetic` will make one of the instructions that load to memory and either increment
@ -368,9 +343,8 @@ fn make_load_to_address_and_do_arithmetic<
dst: register::Combined,
src: register::SingleEightBit,
make: F,
data: &[u8],
) -> ParseResult {
make_load_and_do_arithmetic(|| make(dst, src), data)
) -> ParseOutput {
make_load_and_do_arithmetic(|| make(dst, src))
}
/// `make_load_to_address_and_do_arithmetic` will make one of the instructions that load from memory and either increment
@ -381,28 +355,20 @@ fn make_load_from_address_and_do_arithmetic<
dst: register::SingleEightBit,
src: register::Combined,
make: F,
data: &[u8],
) -> ParseResult {
make_load_and_do_arithmetic(|| make(dst, src), data)
) -> ParseOutput {
make_load_and_do_arithmetic(|| make(dst, src))
}
/// `make_load_and_do_arithmetic` will parse a `RunnableInstruction` from one of the load+arithmetic isntructions,
/// which will be produced by the given `make` function
fn make_load_and_do_arithmetic<F: Fn() -> EightBitLoadInstruction>(
make: F,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
let load_instruction = make();
fn make_load_and_do_arithmetic<F: Fn() -> EightBitLoadInstruction>(make: F) -> ParseOutput {
let load_instruction = make();
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(load_instruction),
cycles: 8,
},
remaining_data,
)
})
.ok_or(Error::NoData)
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(load_instruction),
cycles: 8,
},
1,
)
}

View File

@ -1,78 +1,80 @@
use crate::register;
use crate::cpu::instructions::load8::EightBitLoadInstruction;
use crate::cpu::instructions::Instruction;
use crate::cpu::parse::ParseOutput;
use crate::cpu::{
instructions::RunnableInstruction,
parse::{self, Error, OpcodeParser, ParseResult},
};
use crate::memory::View;
use crate::register;
pub struct Between8BitRegisterParser;
impl OpcodeParser for Between8BitRegisterParser {
/// Parses an opcode that transfers an 8bit values between single 8 bit registers.
fn parse_opcode(data: &[u8]) -> ParseResult {
fn parse_opcode(data: &View) -> ParseResult {
// I don't normally like doing this but this function was ridiculous with the line length wrapping
// caused by not doing it
#[allow(clippy::enum_glob_use)]
use register::SingleEightBit::*;
let opcode = parse::get_opcode_from_data(data)?;
let opcode = parse::get_opcode_from_data(data);
match opcode {
0x7f => make_load_between_register_data(A, A, data),
0x78 => make_load_between_register_data(A, B, data),
0x79 => make_load_between_register_data(A, C, data),
0x7A => make_load_between_register_data(A, D, data),
0x7B => make_load_between_register_data(A, E, data),
0x7C => make_load_between_register_data(A, H, data),
0x7D => make_load_between_register_data(A, L, data),
0x7f => Ok(make_load_between_register_data(A, A)),
0x78 => Ok(make_load_between_register_data(A, B)),
0x79 => Ok(make_load_between_register_data(A, C)),
0x7A => Ok(make_load_between_register_data(A, D)),
0x7B => Ok(make_load_between_register_data(A, E)),
0x7C => Ok(make_load_between_register_data(A, H)),
0x7D => Ok(make_load_between_register_data(A, L)),
0x47 => make_load_between_register_data(B, A, data),
0x40 => make_load_between_register_data(B, B, data),
0x41 => make_load_between_register_data(B, C, data),
0x42 => make_load_between_register_data(B, D, data),
0x43 => make_load_between_register_data(B, E, data),
0x44 => make_load_between_register_data(B, H, data),
0x45 => make_load_between_register_data(B, L, data),
0x47 => Ok(make_load_between_register_data(B, A)),
0x40 => Ok(make_load_between_register_data(B, B)),
0x41 => Ok(make_load_between_register_data(B, C)),
0x42 => Ok(make_load_between_register_data(B, D)),
0x43 => Ok(make_load_between_register_data(B, E)),
0x44 => Ok(make_load_between_register_data(B, H)),
0x45 => Ok(make_load_between_register_data(B, L)),
0x4F => make_load_between_register_data(C, A, data),
0x48 => make_load_between_register_data(C, B, data),
0x49 => make_load_between_register_data(C, C, data),
0x4A => make_load_between_register_data(C, D, data),
0x4B => make_load_between_register_data(C, E, data),
0x4C => make_load_between_register_data(C, H, data),
0x4D => make_load_between_register_data(C, L, data),
0x4F => Ok(make_load_between_register_data(C, A)),
0x48 => Ok(make_load_between_register_data(C, B)),
0x49 => Ok(make_load_between_register_data(C, C)),
0x4A => Ok(make_load_between_register_data(C, D)),
0x4B => Ok(make_load_between_register_data(C, E)),
0x4C => Ok(make_load_between_register_data(C, H)),
0x4D => Ok(make_load_between_register_data(C, L)),
0x57 => make_load_between_register_data(D, A, data),
0x50 => make_load_between_register_data(D, B, data),
0x51 => make_load_between_register_data(D, C, data),
0x52 => make_load_between_register_data(D, D, data),
0x53 => make_load_between_register_data(D, E, data),
0x54 => make_load_between_register_data(D, H, data),
0x55 => make_load_between_register_data(D, L, data),
0x57 => Ok(make_load_between_register_data(D, A)),
0x50 => Ok(make_load_between_register_data(D, B)),
0x51 => Ok(make_load_between_register_data(D, C)),
0x52 => Ok(make_load_between_register_data(D, D)),
0x53 => Ok(make_load_between_register_data(D, E)),
0x54 => Ok(make_load_between_register_data(D, H)),
0x55 => Ok(make_load_between_register_data(D, L)),
0x5F => make_load_between_register_data(E, A, data),
0x58 => make_load_between_register_data(E, B, data),
0x59 => make_load_between_register_data(E, C, data),
0x5A => make_load_between_register_data(E, D, data),
0x5B => make_load_between_register_data(E, E, data),
0x5C => make_load_between_register_data(E, H, data),
0x5D => make_load_between_register_data(E, L, data),
0x5F => Ok(make_load_between_register_data(E, A)),
0x58 => Ok(make_load_between_register_data(E, B)),
0x59 => Ok(make_load_between_register_data(E, C)),
0x5A => Ok(make_load_between_register_data(E, D)),
0x5B => Ok(make_load_between_register_data(E, E)),
0x5C => Ok(make_load_between_register_data(E, H)),
0x5D => Ok(make_load_between_register_data(E, L)),
0x67 => make_load_between_register_data(H, A, data),
0x60 => make_load_between_register_data(H, B, data),
0x61 => make_load_between_register_data(H, C, data),
0x62 => make_load_between_register_data(H, D, data),
0x63 => make_load_between_register_data(H, E, data),
0x64 => make_load_between_register_data(H, H, data),
0x65 => make_load_between_register_data(H, L, data),
0x67 => Ok(make_load_between_register_data(H, A)),
0x60 => Ok(make_load_between_register_data(H, B)),
0x61 => Ok(make_load_between_register_data(H, C)),
0x62 => Ok(make_load_between_register_data(H, D)),
0x63 => Ok(make_load_between_register_data(H, E)),
0x64 => Ok(make_load_between_register_data(H, H)),
0x65 => Ok(make_load_between_register_data(H, L)),
0x6F => make_load_between_register_data(L, A, data),
0x68 => make_load_between_register_data(L, B, data),
0x69 => make_load_between_register_data(L, C, data),
0x6A => make_load_between_register_data(L, D, data),
0x6B => make_load_between_register_data(L, E, data),
0x6c => make_load_between_register_data(L, H, data),
0x6D => make_load_between_register_data(L, L, data),
0x6F => Ok(make_load_between_register_data(L, A)),
0x68 => Ok(make_load_between_register_data(L, B)),
0x69 => Ok(make_load_between_register_data(L, C)),
0x6A => Ok(make_load_between_register_data(L, D)),
0x6B => Ok(make_load_between_register_data(L, E)),
0x6c => Ok(make_load_between_register_data(L, H)),
0x6D => Ok(make_load_between_register_data(L, L)),
_ => Err(Error::UnknownOpcode(opcode)),
}
@ -82,19 +84,15 @@ impl OpcodeParser for Between8BitRegisterParser {
fn make_load_between_register_data(
dst: register::SingleEightBit,
src: register::SingleEightBit,
data: &[u8],
) -> ParseResult {
data.get(1..)
.map(|remaining_data| {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(
EightBitLoadInstruction::LoadBetweenRegisters { dst, src },
),
cycles: 4,
},
remaining_data,
)
})
.ok_or(Error::NoData)
) -> ParseOutput {
(
RunnableInstruction {
instruction: Instruction::EightBitLoad(EightBitLoadInstruction::LoadBetweenRegisters {
dst,
src,
}),
cycles: 4,
},
1,
)
}

View File

@ -1,9 +1,12 @@
pub use self::view::{GetTuple as GetViewTuple, View};
use thiserror::Error;
/// The location at which the IO registers start in memory. (see 2.13.1 of the manual)
pub const IO_REGISTER_START_ADDRESS: usize = 0xFF00;
const MAX_MEMORY_ADDRESS: usize = 0xFFFF;
mod view;
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to fetch from invalid address {0:X}")]
@ -34,12 +37,12 @@ impl Memory {
.ok_or(Error::GetInvalidAddress(index))
}
pub fn get_from(&self, index: usize) -> Result<&[u8], Error> {
pub fn get_from(&self, index: usize) -> Result<View, Error> {
if index > self.data.len() {
return Err(Error::GetInvalidAddress(index));
}
Ok(&self.data[index..])
Ok(View::new(self, index))
}
/// `set` will get a single byte value at the given address, and return the new value

303
src/memory/view.rs Normal file
View File

@ -0,0 +1,303 @@
use super::Memory;
/// Holds a view of memory, starting at some offset
pub struct View<'a> {
underlying_memory: &'a [u8],
start: usize,
}
impl<'a> View<'a> {
pub(crate) fn new(memory: &'a Memory, start: usize) -> Self {
Self {
underlying_memory: &memory.data,
start,
}
}
pub(crate) fn new_from_data(memory: &'a [u8], start: usize) -> Self {
Self {
underlying_memory: memory,
start,
}
}
/// Get the first value in this view
pub fn get(&self) -> u8 {
self.get_index(0)
}
/// Get the nth value in this view. This will wrap around if index is too large
pub fn get_index(&self, index: usize) -> u8 {
let data_length = self.underlying_memory.len();
self.underlying_memory[(self.start + index) % data_length]
}
/// Construct a new view that has been advanced by `by` values. This will wrap around if index is too large
pub fn advance(self, by: usize) -> Self {
let data_length = self.underlying_memory.len();
let next_idx = (self.start + by) % data_length;
View {
underlying_memory: self.underlying_memory,
start: next_idx,
}
}
}
pub trait GetTuple<T> {
/// Get the type this is implemented on as a tuple, as dictated by type T
fn get_tuple(&self) -> T;
}
impl<T: ViewIntoTuple> GetTuple<T> for View<'_> {
/// Get the first n elements in this view (where n is the length of the tuple in type T).
/// This is implemented for all tuples up to length 6.
///
/// # Examples
/// ```ignore
/// # // This can't run because memory isn't public
///
/// let mut memory = Memory::new();
/// memory.set(0xF0, 10).expect("failed to set memory value");
/// memory.set(0xF1, 11).expect("failed to set memory value");
/// memory.set(0xF2, 12).expect("failed to set memory value");
/// memory.set(0xF3, 13).expect("failed to set memory value");
/// let view = View::new(&memory, 0xF0);
/// assert_eq!(10, view.get_index(0));
/// assert_eq!(11, view.get_index(1));
/// assert_eq!(12, view.get_index(2));
/// assert_eq!(13, view.get_index(3));
/// ```
fn get_tuple(&self) -> T {
T::into_tuple(self)
}
}
/**
* The following macro implementation is "heavily inspired" (read: stolen with many tweaks) by itertools' `tuples` method.
*
* The implementation is <https://github.com/rust-itertools/itertools/blob/ad2e401862d80d34e380d343758f71a533dcf36b/src/tuple_impl.rs#L266-L331>
*
* itertools is licensed under the MIT license, which is expanded upon here
*
* Copyright (c) 2015
*
* Permission is hereby granted, free of charge, to any
* person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the
* Software without restriction, including without
* limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice
* shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
macro_rules! get_for_each_ident{
($arr:ident, $n: expr,) => {};
($arr:ident, $($i:ident,)*) => {
get_for_each_ident!($arr, 0, $($i,)*);
};
($view:ident, $n: expr, $i0:ident, $($i:ident,)*) => {
$i0 = $view.get_index($n);
get_for_each_ident!($view, $n + 1, $($i,)*);
};
}
/// Convert an identity and a tokentree to just that tree. Useful to abuse wildcard semantics.
macro_rules! ignore_ident{
($id:ident, $($t:tt)*) => {$($t)*};
}
// this trait could _PROBABLY_ be converted into From<&View<'_>> with some trickery but the lifetimes
// are making my head hurt and I don't think it's worth it for an implementation detail like this
trait ViewIntoTuple {
fn into_tuple(view: &View<'_>) -> Self;
}
/*
A implementation implemtnation of what the below macro will implement
impl <T: Copy + Default> TupleGetFrom<T> for (T, T, T) {
fn get_from(arr: &[T]) -> Self {
let (mut a, mut b, mut c) = Self::default();
a = arr[0];
b = arr[1];
c = arr[2];
(a, b, c)
}
}
*/
macro_rules! impl_tuple_get_from {
($_: ident, $__: ident,) => {};
// We recursively implement this for all tuples > 2
($_: ident, $($idents: ident,)+) => {
impl_tuple_get_from!($($idents,)*);
// implement the trait - we abuse the wildcard here with ignore_ident to implement on the appropriate length
// u8 tuple - the idea here is this becomes (ignore_ident!(a, u8), ignore_ident!(b, u8), ...), which expands
// to just (u8, u8...)
impl ViewIntoTuple for ($(ignore_ident!($idents, u8),)*) {
fn into_tuple(view: &View<'_>) -> Self {
// Create a tuple of the correct length; View uses u8s, which has a default
// (though this could be implemented generically with little effort)
#[allow(unused_assignments)]
let ($(mut $idents, )*) = Self::default();
// Perform a View::get for each identity
get_for_each_ident!(view, $($idents, )*);
($($idents, )*)
}
}
}
}
// and kick off the implementation here
impl_tuple_get_from!(ignore, a, b, c, d, e, f,);
#[cfg(test)]
mod tests {
use crate::memory::MAX_MEMORY_ADDRESS;
use super::*;
#[test]
fn test_can_get_single_value() {
let mut memory = Memory::new();
memory.set(0xF0, 10).expect("failed to set memory value");
let view = View::new(&memory, 0xF0);
assert_eq!(10, view.get());
}
#[test]
fn test_can_get_single_value_by_index() {
let mut memory = Memory::new();
memory.set(0xF0, 10).expect("failed to set memory value");
memory.set(0xF1, 11).expect("failed to set memory value");
memory.set(0xF2, 12).expect("failed to set memory value");
memory.set(0xF3, 13).expect("failed to set memory value");
let view = View::new(&memory, 0xF0);
assert_eq!(10, view.get_index(0));
assert_eq!(11, view.get_index(1));
assert_eq!(12, view.get_index(2));
assert_eq!(13, view.get_index(3));
}
#[test]
fn get_can_advance_view() {
let mut memory = Memory::new();
memory.set(0xF0, 10).expect("failed to set memory value");
memory.set(0xF1, 11).expect("failed to set memory value");
memory.set(0xF2, 12).expect("failed to set memory value");
memory.set(0xF3, 13).expect("failed to set memory value");
let view = View::new(&memory, 0xF0);
let advanced_view = view.advance(1);
assert_eq!(11, advanced_view.get_index(0));
assert_eq!(12, advanced_view.get_index(1));
assert_eq!(13, advanced_view.get_index(2));
}
#[test]
fn get_can_advance_view_with_wraparound() {
let mut memory = Memory::new();
memory.set(0x01, 10).expect("failed to set memory value");
let view = View::new(&memory, MAX_MEMORY_ADDRESS);
let advanced_view = view.advance(2);
assert_eq!(10, advanced_view.get());
}
#[test]
fn test_can_get_two_values() {
let mut memory = Memory::new();
memory.set(0xF0, 10).expect("failed to set memory value");
memory.set(0xF1, 11).expect("failed to set memory value");
memory.set(0xF2, 12).expect("failed to set memory value");
memory.set(0xF3, 13).expect("failed to set memory value");
let view = View::new(&memory, 0xF0);
let (a, b) = view.get_tuple();
assert_eq!(a, 10);
assert_eq!(b, 11);
}
#[test]
fn test_can_get_three_values() {
let mut memory = Memory::new();
memory.set(0xF0, 10).expect("failed to set memory value");
memory.set(0xF1, 11).expect("failed to set memory value");
memory.set(0xF2, 12).expect("failed to set memory value");
memory.set(0xF3, 13).expect("failed to set memory value");
let view = View::new(&memory, 0xF0);
let (a, b, c) = view.get_tuple();
assert_eq!(a, 10);
assert_eq!(b, 11);
assert_eq!(c, 12);
}
#[test]
fn test_can_get_four_values() {
let mut memory = Memory::new();
memory.set(0xF0, 10).expect("failed to set memory value");
memory.set(0xF1, 11).expect("failed to set memory value");
memory.set(0xF2, 12).expect("failed to set memory value");
memory.set(0xF3, 13).expect("failed to set memory value");
let view = View::new(&memory, 0xF0);
let (a, b, c, d) = view.get_tuple();
assert_eq!(a, 10);
assert_eq!(b, 11);
assert_eq!(c, 12);
assert_eq!(d, 13);
}
#[test]
fn test_getting_more_values_than_in_view_wraps_around() {
let mut memory = Memory::new();
memory.set(0, 100).expect("failed to set memory value");
memory
.set(MAX_MEMORY_ADDRESS - 4, 10)
.expect("failed to set memory value");
memory
.set(MAX_MEMORY_ADDRESS - 3, 11)
.expect("failed to set memory value");
memory
.set(MAX_MEMORY_ADDRESS - 2, 12)
.expect("failed to set memory value");
memory
.set(MAX_MEMORY_ADDRESS - 1, 13)
.expect("failed to set memory value");
memory
.set(MAX_MEMORY_ADDRESS, 14)
.expect("failed to set memory value");
let view = View::new(&memory, MAX_MEMORY_ADDRESS - 4);
#[allow(clippy::many_single_char_names)]
let (a, b, c, d, e, f) = view.get_tuple();
assert_eq!(a, 10);
assert_eq!(b, 11);
assert_eq!(c, 12);
assert_eq!(d, 13);
assert_eq!(e, 14);
assert_eq!(f, 100);
}
}