use std::fmt::Debug;
use derive_more::UpperHex;
use strum_macros::{Display, EnumDiscriminants, EnumIter};
use thiserror::Error;
use ux::{u12, u4};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Register(pub u4);
impl From<Register> for usize {
fn from(register: Register) -> Self {
usize::try_from(register.0).unwrap()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Byte {
Register(Register),
Immediate(u8),
}
#[derive(UpperHex)]
#[upper_hex(fmt = "UpperHex")]
enum Address {
Address(u12),
LongAddress(u16),
}
impl Default for Byte {
fn default() -> Self {
Self::Immediate(u8::default())
}
}
impl From<Byte> for u8 {
fn from(byte: Byte) -> Self {
match byte {
Byte::Register(Register(x)) => u8::from(x),
Byte::Immediate(x) => x,
}
}
}
#[non_exhaustive]
#[derive(EnumIter, EnumDiscriminants, Copy, Clone, Debug, Display, PartialEq, Eq)]
#[strum_discriminants(derive(Display))]
pub enum Instruction {
Halt,
Exit(Option<u8>),
ScrollUp(u4),
ScrollDown(u4),
ScrollRight,
ScrollLeft,
Clear,
Return,
ToggleLoadStoreQuirk,
LoRes,
HiRes,
CallMachineCode(u12),
Jump(u12),
Call(u12),
SkipIfEqual(Register, Byte),
SkipIfNotEqual(Register, Byte),
Add(Register, Byte),
Set(Register, Byte),
Or(Register, Register),
And(Register, Register),
Xor(Register, Register),
Sub(Register, Register),
ShiftRight(Register, Register),
ShiftLeft(Register, Register),
SubReverse(Register, Register),
SetIndex(u12),
SetIndexLong(u16),
JumpRelative(u12),
Random(Register, u8),
Draw(Register, Register, u4),
SkipKey(Register),
SkipNotKey(Register),
LoadAudio,
LoadDelay(Register),
BlockKey(Register),
SelectPlane(u4),
SetPitch(Register),
SetDelay(Register),
SetSound(Register),
AddRegisterToIndex(Register),
FontCharacter(Register),
BigFontCharacter(Register),
Bcd(Register),
Store(Register),
Load(Register),
StoreRange(Register, Register),
LoadRange(Register, Register),
StoreFlags(Register),
LoadFlags(Register),
}
#[non_exhaustive]
#[derive(EnumIter, Copy, Clone, Debug, Display, UpperHex, PartialEq, Eq)]
#[upper_hex(fmt = "UpperHex")]
pub enum Opcode {
Opcode(u16),
LongOpcode(u32),
}
impl From<Instruction> for Opcode {
fn from(instruction: Instruction) -> Opcode {
match instruction {
Instruction::Halt => Opcode::Opcode(0x0000),
Instruction::Exit(None) => Opcode::Opcode(0x00FD),
Instruction::Exit(Some(n)) => Opcode::Opcode(0x0010 | u16::from(n)),
Instruction::ScrollDown(n) => Opcode::Opcode(0x00C0 | u16::from(n)),
Instruction::ScrollUp(n) => Opcode::Opcode(0x00D0 | u16::from(n)),
Instruction::ToggleLoadStoreQuirk => Opcode::Opcode(0x00FA),
Instruction::ScrollRight => Opcode::Opcode(0x00FB),
Instruction::ScrollLeft => Opcode::Opcode(0x00FC),
Instruction::LoRes => Opcode::Opcode(0x00FE),
Instruction::HiRes => Opcode::Opcode(0x00FF),
Instruction::Clear => Opcode::Opcode(0x00E0),
Instruction::Return => Opcode::Opcode(0x00EE),
Instruction::CallMachineCode(nnn) => Opcode::Opcode(u16::from(nnn)),
Instruction::Jump(nnn) => Opcode::Opcode(0x1000 | u16::from(nnn)),
Instruction::Call(nnn) => Opcode::Opcode(0x2000 | u16::from(nnn)),
Instruction::SkipIfEqual(Register(x), Byte::Immediate(kk)) => {
Opcode::Opcode(0x3000 | (u16::from(x) << 8) | u16::from(kk))
}
Instruction::SkipIfEqual(Register(x), Byte::Register(Register(y))) => {
Opcode::Opcode(0x5000 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::SkipIfNotEqual(Register(x), Byte::Immediate(kk)) => {
Opcode::Opcode(0x4000 | (u16::from(x) << 8) | u16::from(kk))
}
Instruction::SkipIfNotEqual(Register(x), Byte::Register(Register(y))) => {
Opcode::Opcode(0x9000 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::Set(Register(x), Byte::Immediate(kk)) => {
Opcode::Opcode(0x6000 | (u16::from(x) << 8) | u16::from(kk))
}
Instruction::Add(Register(x), Byte::Immediate(kk)) => {
Opcode::Opcode(0x7000 | (u16::from(x) << 8) | u16::from(kk))
}
Instruction::Set(Register(x), Byte::Register(Register(y))) => {
Opcode::Opcode(0x8000 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::Add(Register(x), Byte::Register(Register(y))) => {
Opcode::Opcode(0x8004 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::Or(Register(x), Register(y)) => {
Opcode::Opcode(0x8001 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::And(Register(x), Register(y)) => {
Opcode::Opcode(0x8002 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::Xor(Register(x), Register(y)) => {
Opcode::Opcode(0x8003 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::Sub(Register(x), Register(y)) => {
Opcode::Opcode(0x8005 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::ShiftRight(Register(x), Register(y)) => {
Opcode::Opcode(0x8006 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::ShiftLeft(Register(x), Register(y)) => {
Opcode::Opcode(0x800E | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::SubReverse(Register(x), Register(y)) => {
Opcode::Opcode(0x8007 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::SetIndex(nnn) => Opcode::Opcode(0xA000 | u16::from(nnn)),
Instruction::SetIndexLong(nnnn) => Opcode::LongOpcode(0xF000_0000 | u32::from(nnnn)),
Instruction::JumpRelative(nnn) => Opcode::Opcode(0xB000 | u16::from(nnn)),
Instruction::Random(Register(x), kk) => {
Opcode::Opcode(0xC000 | (u16::from(x) << 8) | u16::from(kk))
}
Instruction::Draw(Register(x), Register(y), n) => {
Opcode::Opcode(0xD000 | (u16::from(x) << 8) | (u16::from(y) << 4) | u16::from(n))
}
Instruction::SkipKey(Register(x)) => Opcode::Opcode(0xE09E | (u16::from(x) << 8)),
Instruction::SkipNotKey(Register(x)) => Opcode::Opcode(0xE0A1 | (u16::from(x) << 8)),
Instruction::LoadAudio => Opcode::Opcode(0xF002),
Instruction::LoadDelay(Register(x)) => Opcode::Opcode(0xF007 | (u16::from(x) << 8)),
Instruction::BlockKey(Register(x)) => Opcode::Opcode(0xF00A | (u16::from(x) << 8)),
Instruction::SelectPlane(n) => Opcode::Opcode(0xF001 | (u16::from(n) << 8)),
Instruction::SetPitch(Register(x)) => Opcode::Opcode(0xF03A | (u16::from(x) << 8)),
Instruction::SetDelay(Register(x)) => Opcode::Opcode(0xF015 | (u16::from(x) << 8)),
Instruction::SetSound(Register(x)) => Opcode::Opcode(0xF018 | (u16::from(x) << 8)),
Instruction::AddRegisterToIndex(Register(x)) => {
Opcode::Opcode(0xF01E | (u16::from(x) << 8))
}
Instruction::FontCharacter(Register(x)) => Opcode::Opcode(0xF029 | (u16::from(x) << 8)),
Instruction::BigFontCharacter(Register(x)) => {
Opcode::Opcode(0xF030 | (u16::from(x) << 8))
}
Instruction::Bcd(Register(x)) => Opcode::Opcode(0xF033 | (u16::from(x) << 8)),
Instruction::Store(Register(x)) => Opcode::Opcode(0xF055 | (u16::from(x) << 8)),
Instruction::Load(Register(x)) => Opcode::Opcode(0xF065 | (u16::from(x) << 8)),
Instruction::StoreFlags(Register(x)) => Opcode::Opcode(0xF075 | (u16::from(x) << 8)),
Instruction::LoadFlags(Register(x)) => Opcode::Opcode(0xF085 | (u16::from(x) << 8)),
Instruction::StoreRange(Register(x), Register(y)) => {
Opcode::Opcode(0x5002 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
Instruction::LoadRange(Register(x), Register(y)) => {
Opcode::Opcode(0x5003 | (u16::from(x) << 8) | (u16::from(y) << 4))
}
}
}
}
#[derive(Error)]
pub enum DecodeError {
#[error("unknown opcode: {0}")]
UnknownOpcodeError(Opcode),
#[error("incomplete opcode: {0}")]
IncompleteLongOpcodeError(InstructionDiscriminants, Box<dyn Fn(u16) -> Instruction>),
}
impl Debug for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnknownOpcodeError(arg0) => {
f.debug_tuple("UnknownOpcodeError").field(arg0).finish()
}
Self::IncompleteLongOpcodeError(arg0, arg1) => f
.debug_tuple("IncompleteLongOpcodeError")
.field(arg0)
.finish(),
}
}
}
impl TryFrom<Opcode> for Instruction {
type Error = DecodeError;
fn try_from(op: Opcode) -> Result<Self, Self::Error> {
match op {
Opcode::LongOpcode(opcode) => {
let prefix = u16::try_from(opcode >> 16).unwrap();
let suffix = u16::try_from(opcode & 0x0000_FFFF).unwrap();
if prefix == 0xF000 {
Ok(Instruction::SetIndexLong(suffix))
} else {
Err(DecodeError::UnknownOpcodeError(op))
}
}
Opcode::Opcode(opcode) => {
let x = u4::try_from((opcode & 0x0F00) >> 8).unwrap();
let y = u4::try_from((opcode & 0x00F0) >> 4).unwrap();
let nnn = u12::try_from(opcode & 0x0FFF).unwrap();
let kk = (opcode & 0x00FF) as u8;
let n = u4::try_from(opcode & 0x000F).unwrap();
let op1 = (opcode & 0xF000) >> 12;
let op2 = (opcode & 0x0F00) >> 8;
let op3 = (opcode & 0x00F0) >> 4;
let op4 = opcode & 0x000F;
Ok(
match (op1, op2, op3, op4) {
#![allow(clippy::match_same_arms)]
(0x0, 0x0, 0x0, 0x0) => Instruction::Halt,
(0x0, 0x0, 0x1, _n) => Instruction::Exit(Some(op4 as u8)),
(0x0, 0x0, 0xB, _n) => Instruction::ScrollUp(n),
(0x0, 0x0, 0xC, _n) => Instruction::ScrollDown(n),
(0x0, 0x0, 0xD, _n) => Instruction::ScrollUp(n),
(0x0, 0x0, 0xE, 0x0) => Instruction::Clear,
(0x0, 0x0, 0xE, 0xE) => Instruction::Return,
(0x0, 0x0, 0xF, 0xA) => Instruction::ToggleLoadStoreQuirk,
(0x0, 0x0, 0xF, 0xB) => Instruction::ScrollRight,
(0x0, 0x0, 0xF, 0xC) => Instruction::ScrollLeft,
(0x0, 0x0, 0xF, 0xD) => Instruction::Exit(None),
(0x0, 0x0, 0xF, 0xE) => Instruction::LoRes,
(0x0, 0x0, 0xF, 0xF) => Instruction::HiRes,
(0x0, _, _, _) => Instruction::CallMachineCode(nnn),
(0x1, _, _, _) => Instruction::Jump(nnn),
(0x2, _, _, _) => Instruction::Call(nnn),
(0x3, _x, _, _) => {
Instruction::SkipIfEqual(Register(x), Byte::Immediate(kk))
}
(0x4, _x, _, _) => {
Instruction::SkipIfNotEqual(Register(x), Byte::Immediate(kk))
}
(0x5, _x, _y, 0x0) => {
Instruction::SkipIfEqual(Register(x), Byte::Register(Register(y)))
}
(0x5, _x, _y, 0x2) => Instruction::StoreRange(Register(x), Register(y)),
(0x5, _x, _y, 0x3) => Instruction::LoadRange(Register(x), Register(y)),
(0x6, _x, _, _) => Instruction::Set(Register(x), Byte::Immediate(kk)),
(0x7, _x, _, _) => Instruction::Add(Register(x), Byte::Immediate(kk)),
(0x8, _x, _y, 0) => {
Instruction::Set(Register(x), Byte::Register(Register(y)))
}
(0x8, _x, _y, 1) => Instruction::Or(Register(x), Register(y)),
(0x8, _x, _y, 2) => Instruction::And(Register(x), Register(y)),
(0x8, _x, _y, 3) => Instruction::Xor(Register(x), Register(y)),
(0x8, _x, _y, 4) => {
Instruction::Add(Register(x), Byte::Register(Register(y)))
}
(0x8, _x, _y, 5) => Instruction::Sub(Register(x), Register(y)),
(0x8, _x, _y, 0x6) => Instruction::ShiftRight(Register(x), Register(y)),
(0x8, _x, _y, 0x7) => Instruction::SubReverse(Register(x), Register(y)),
(0x8, _x, _y, 0xE) => Instruction::ShiftLeft(Register(x), Register(y)),
(0x9, _x, _y, 0x0) => {
Instruction::SkipIfNotEqual(Register(x), Byte::Register(Register(y)))
}
(0xA, _, _, _) => Instruction::SetIndex(nnn),
(0xB, _, _, _) => Instruction::JumpRelative(nnn),
(0xC, _x, _, _) => Instruction::Random(Register(x), kk),
(0xD, _x, _y, _n) => Instruction::Draw(Register(x), Register(y), n),
(0xE, _x, 0x9, 0xE) => Instruction::SkipKey(Register(x)),
(0xE, _x, 0xA, 0x1) => Instruction::SkipNotKey(Register(x)),
(0xF, 0x0, 0x0, 0x0) => {
return Err(DecodeError::IncompleteLongOpcodeError(
InstructionDiscriminants::SetIndexLong,
Box::new(Instruction::SetIndexLong),
));
}
(0xF, 0x0, 0x0, 0x2) => Instruction::LoadAudio,
(0xF, _x, 0x0, 0x7) => Instruction::LoadDelay(Register(x)),
(0xF, _x, 0x0, 0xA) => Instruction::BlockKey(Register(x)),
(0xF, _n, 0x0, 0x1) => Instruction::SelectPlane(x),
(0xF, _x, 0x1, 0x5) => Instruction::SetDelay(Register(x)),
(0xF, _x, 0x1, 0x8) => Instruction::SetSound(Register(x)),
(0xF, _x, 0x1, 0xE) => Instruction::AddRegisterToIndex(Register(x)),
(0xF, _x, 0x2, 0x9) => Instruction::FontCharacter(Register(x)),
(0xF, _x, 0x3, 0x0) => Instruction::BigFontCharacter(Register(x)),
(0xF, _x, 0x3, 0x3) => Instruction::Bcd(Register(x)),
(0xF, _x, 0x3, 0xA) => Instruction::SetPitch(Register(x)),
(0xF, _x, 0x5, 0x5) => Instruction::Store(Register(x)),
(0xF, _x, 0x6, 0x5) => Instruction::Load(Register(x)),
(0xF, _x, 0x7, 0x5) => Instruction::StoreFlags(Register(x)),
(0xF, _x, 0x8, 0x5) => Instruction::LoadFlags(Register(x)),
_ => return Err(DecodeError::UnknownOpcodeError(op)), },
)
}
}
}
}