From 33da1089a2da15288a3459658d7d667243ee0432 Mon Sep 17 00:00:00 2001 From: John Breaux Date: Sun, 23 Apr 2023 12:01:47 -0500 Subject: [PATCH] AnyRange: Add AnyRange for taking any range in error --- src/cpu.rs | 29 +++++++++------ src/error.rs | 23 ++++++------ src/error/any_range.rs | 69 +++++++++++++++++++++++++++++++++++ src/error/any_range/macros.rs | 33 +++++++++++++++++ 4 files changed, 131 insertions(+), 23 deletions(-) create mode 100644 src/error/any_range.rs create mode 100644 src/error/any_range/macros.rs diff --git a/src/cpu.rs b/src/cpu.rs index 4d9c450..08a4ce3 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -476,22 +476,27 @@ impl CPU { return Ok(self); } self.cycle += 1; + let opchunk = self + .screen + .get(self.pc as usize..) + .ok_or(Error::InvalidAddressRange { + range: (self.pc as usize..).into(), + })?; // fetch opcode - let opcode: &[u8; 2] = if let Some(slice) = bus.get(self.pc as usize..self.pc as usize + 2) - { - slice - .try_into() - .expect("`slice` should be exactly 2 bytes.") - } else { - return Err(Error::InvalidAddressRange { - range: self.pc as usize..self.pc as usize + 2, - }); - }; + let opcode: &[u8; 2] = + if let Some(slice) = self.screen.get(self.pc as usize..self.pc as usize + 2) { + slice + .try_into() + .expect("`slice` should be exactly 4 bytes.") + } else { + return Err(Error::InvalidAddressRange { + range: (self.pc as usize..self.pc as usize + 4).into(), + }); + }; // Print opcode disassembly: if self.flags.debug { - self.timers.insn = Instant::now(); - std::print!( + std::println!( "{:3} {:03x}: {:<36}", self.cycle.bright_black(), self.pc, diff --git a/src/error.rs b/src/error.rs index b6b3414..1247841 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,8 @@ //! Error type for Chirp -use std::ops::Range; +pub mod any_range; +use any_range::AnyRange; use crate::cpu::bus::Region; use thiserror::Error; @@ -15,7 +16,7 @@ pub type Result = std::result::Result; #[derive(Debug, Error)] pub enum Error { /// Represents a breakpoint being hit - #[error("Breakpoint hit: {addr:03x} ({next:04x})")] + #[error("breakpoint hit: {addr:03x} ({next:04x})")] BreakpointHit { /// The address of the breakpoint addr: u16, @@ -23,37 +24,37 @@ pub enum Error { next: u16, }, /// Represents an unimplemented operation - #[error("Unrecognized opcode: {word:04x}")] + #[error("opcode {word:04x} not recognized")] UnimplementedInstruction { /// The offending word word: u16, }, /// The region you asked for was not defined - #[error("No {region} found on bus")] + #[error("region {region} is not present on bus")] MissingRegion { /// The offending [Region] region: Region, }, - /// Tried to fetch [Range] from bus, received nothing - #[error("Invalid range {range:04x?} for bus")] + /// Tried to fetch data at [AnyRange] from bus, received nothing + #[error("range {range:04x?} is not present on bus")] InvalidAddressRange { - /// The offending [Range] - range: Range, + /// The offending [AnyRange] + range: AnyRange, }, /// Tried to press a key that doesn't exist - #[error("Invalid key: {key:X}")] + #[error("tried to press key {key:X} which does not exist")] InvalidKey { /// The offending key key: usize, }, /// Tried to get/set an out-of-bounds register - #[error("Invalid register: v{reg:X}")] + #[error("tried to access register v{reg:X} which does not exist")] InvalidRegister { /// The offending register reg: usize, }, /// Tried to convert string into mode, but it did not match. - #[error("Invalid mode: {mode}")] + #[error("no suitable conversion of \"{mode}\" into Mode")] InvalidMode { /// The string which failed to become a mode mode: String, diff --git a/src/error/any_range.rs b/src/error/any_range.rs new file mode 100644 index 0000000..49a3b9c --- /dev/null +++ b/src/error/any_range.rs @@ -0,0 +1,69 @@ +// (c) 2023 John A. Breaux +// This code is licensed under MIT license (see LICENSE for details) + +//! Holder for any [Range] + +// This is super over-engineered, considering it was originally meant to help with only one LOC +use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; +use thiserror::Error; + +#[macro_use] +mod macros; + +#[derive(Clone, Debug, Error, PartialEq, Eq, Hash)] +#[error("Failed to convert variant {0} back into range.")] +/// Emitted when conversion back into a [std::ops]::Range\* fails. +pub struct AnyRangeError(AnyRange); + +/// Holder for any [Range] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum AnyRange { + /// Bounded exclusive [Range] i.e. `0..10` + Range(Range), + /// Unbounded [RangeFrom], i.e. `0..` + RangeFrom(RangeFrom), + /// Unbounded [RangeFull], i.e. `..` + RangeFull(RangeFull), + /// Bounded inclusive [RangeInclusive], i.e. `0..=10` + RangeInclusive(RangeInclusive), + /// Unbounded [RangeTo], i.e. `..10` + RangeTo(RangeTo), + /// Unbounded inclusive [RangeToInclusive], i.e. `..=10` + RangeToInclusive(RangeToInclusive), +} + +variant_from! { + match impl (From, TryInto) for AnyRange { + type Error = AnyRangeError; + Range => AnyRange::Range, + RangeFrom => AnyRange::RangeFrom, + RangeFull => AnyRange::RangeFull, + RangeInclusive => AnyRange::RangeInclusive, + RangeTo => AnyRange::RangeTo, + RangeToInclusive => AnyRange::RangeToInclusive, + } +} + +/// Convenient conversion functions from [AnyRange] to the inner type +impl AnyRange { + try_into_fn! { + /// Converts from [AnyRange::Range] into a [Range], else [None] + pub fn range(self) -> Option>; + /// Converts from [AnyRange::RangeFrom] into a [RangeFrom], else [None] + pub fn range_from(self) -> Option>; + /// Converts from [AnyRange::RangeFull] into a [RangeFull], else [None] + pub fn range_full(self) -> Option; + /// Converts from [AnyRange::RangeInclusive] into a [RangeInclusive], else [None] + pub fn range_inclusive(self) -> Option>; + /// Converts from [AnyRange::RangeTo] into a [RangeTo], else [None] + pub fn range_to(self) -> Option>; + /// Converts from [AnyRange::RangeToInclusive] into a [RangeToInclusive], else [None] + pub fn range_to_inclusive(self) -> Option>; + } +} + +impl From> for AnyRangeError { + fn from(value: AnyRange) -> Self { + AnyRangeError(value) + } +} diff --git a/src/error/any_range/macros.rs b/src/error/any_range/macros.rs new file mode 100644 index 0000000..d465e51 --- /dev/null +++ b/src/error/any_range/macros.rs @@ -0,0 +1,33 @@ +//! Macros for AnyRange + +/// Generate From and TryFrom impls for each variant +macro_rules! variant_from {( + match impl<$T:ident> $_:tt for $enum:path { + type Error = $error:path; + $($src:path => $variant:path),+$(,)? + } +) => { + // The forward direction is infallible + $(impl<$T> From<$src> for $enum { + fn from(value: $src) -> Self { $variant(value) } + })+ + // The reverse direction could fail if the $variant doesn't hold $src + $(impl<$T> TryFrom<$enum> for $src { + type Error = $error; + fn try_from(value: $enum) -> Result { + if let $variant(r) = value { Ok(r) } else { Err(value.into()) } + } + })+ +}} + +/// Turns a list of function prototypes into functions which use [TryInto], returning [Option] +/// +/// # Examples +/// ```rust,ignore +/// try_into_fn! { fn range(self) -> Option; } +/// ``` +macro_rules! try_into_fn { + ($($(#[$doc:meta])? $pub:vis fn $name:ident $args:tt -> $ret:ty);+ $(;)?) => { + $($(#[$doc])? $pub fn $name $args -> $ret { $args.try_into().ok() })+ + } +}