.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "e"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = ["derive_debug"]
|
||||
derive_debug = []
|
||||
big_errors = []
|
||||
|
||||
[dependencies]
|
||||
68
src/exception.rs
Normal file
68
src/exception.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::user_types::UserException;
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use core::mem;
|
||||
use crate::config::USER_ERROR_SIZE;
|
||||
|
||||
#[repr(C, align(0x10))]
|
||||
pub struct Exception {
|
||||
pub(crate) data: [u8; USER_ERROR_SIZE],
|
||||
pub(crate) typ: u16,
|
||||
}
|
||||
|
||||
impl Exception {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is<U: UserException>(&self) -> bool {
|
||||
self.typ == U::get_idx()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn cast_unchecked<U: UserException>(self) -> U {
|
||||
let r = unsafe { (self.data.as_ptr() as *const U).read_unaligned() };
|
||||
mem::forget(self);
|
||||
r
|
||||
}
|
||||
|
||||
/// cast into the provided exception
|
||||
#[inline(always)]
|
||||
pub fn cast<U: UserException>(self) -> Result<U, Self> {
|
||||
if self.is::<U>() {
|
||||
Ok(unsafe { self.cast_unchecked() })
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserException> From<T> for Exception {
|
||||
fn from(value: T) -> Self {
|
||||
UserException::convert(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "derive_debug")]
|
||||
impl Debug for Exception {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
crate::user_types::ERROR_TABLE_START
|
||||
.as_ptr()
|
||||
.cast::<crate::user_types::DebugOrDrop>()
|
||||
.add(self.typ as usize)
|
||||
.read()(self.data.as_ptr() as _, Some(f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Exception {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
crate::user_types::ERROR_TABLE_START
|
||||
.as_ptr()
|
||||
.cast::<crate::user_types::DebugOrDrop>()
|
||||
.add(self.typ as usize)
|
||||
.read()(self.data.as_ptr() as _, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/helpers.rs
Normal file
159
src/helpers.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use core::convert::Infallible;
|
||||
use crate::{Exception, user_types::UserException};
|
||||
use core::ops::{ControlFlow, FromResidual, Try};
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
|
||||
|
||||
pub trait Ret { type Ret; }
|
||||
impl<R> Ret for fn() -> R { type Ret=R; }
|
||||
pub type Never = <fn()->! as Ret>::Ret;
|
||||
|
||||
// Macro to generate the helper enums
|
||||
macro_rules! generate_enum {
|
||||
($name:ident;$($V:ident, $G:ident);+;) => {
|
||||
|
||||
pub enum $name<
|
||||
$($G: UserException),*
|
||||
> {
|
||||
$($V($G),)*
|
||||
Unmatched(Exception)
|
||||
}
|
||||
|
||||
impl< $($G: UserException),* > From<Exception> for $name< $($G),+ > {
|
||||
fn from(value: Exception) -> Self {
|
||||
$(if value.is::<$G>() { Self::$V(unsafe { value.cast_unchecked::<$G>()}) } else)+
|
||||
{ Self::Unmatched(value) }
|
||||
}
|
||||
}
|
||||
|
||||
impl< $($G: UserException),* > From<$name<$($G),+>> for Exception {
|
||||
fn from(value: $name::<$($G),+>) -> Self {
|
||||
match value {
|
||||
$( $name::$V (e) => UserException::convert(e)),+,
|
||||
$name::Unmatched(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl< T, $($G: UserException),* > From<$name<$($G),+>> for crate::Result<T> {
|
||||
fn from(value: $name::<$($G),+>) -> Self {
|
||||
match value {
|
||||
$( $name::$V (e) => Err(UserException::convert(e))),+,
|
||||
$name::Unmatched(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($G: UserException),*> FromResidual for $name<$($G),+> {
|
||||
fn from_residual(residual: <Self as Try>::Residual) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($G: UserException),*> Try for $name<$($G),+> {
|
||||
type Output = Never;
|
||||
type Residual = crate::Result<Infallible>;
|
||||
fn from_output(output: Self::Output) -> Self { unreachable!() }
|
||||
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
|
||||
ControlFlow::Break(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
generate_enum! {
|
||||
M1;
|
||||
V0, A0;
|
||||
}
|
||||
|
||||
generate_enum! {
|
||||
M2;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
}
|
||||
|
||||
generate_enum! {
|
||||
M3;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
V2, A2;
|
||||
}
|
||||
|
||||
generate_enum! {
|
||||
M4;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
V2, A2;
|
||||
V3, A3;
|
||||
}
|
||||
|
||||
|
||||
generate_enum! {
|
||||
M5;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
V2, A2;
|
||||
V3, A3;
|
||||
V4, A4;
|
||||
}
|
||||
|
||||
generate_enum! {
|
||||
M6;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
V2, A2;
|
||||
V3, A3;
|
||||
V4, A4;
|
||||
V5, A5;
|
||||
}
|
||||
|
||||
generate_enum! {
|
||||
M7;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
V2, A2;
|
||||
V3, A3;
|
||||
V4, A4;
|
||||
V5, A5;
|
||||
V6, A6;
|
||||
}
|
||||
|
||||
generate_enum! {
|
||||
M8;
|
||||
V0, A0;
|
||||
V1, A1;
|
||||
V2, A2;
|
||||
V3, A3;
|
||||
V4, A4;
|
||||
V5, A5;
|
||||
V6, A6;
|
||||
V7, A7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub macro alias_macro {
|
||||
($m:ident: $i0:ident) => { use M1 as $m; },
|
||||
($m:ident: $i0:ident, $i1:ident) => { use M2 as $m; },
|
||||
($m:ident: $i0:ident, $i1:ident, $i2:ident) => { use M3 as $m; },
|
||||
|
||||
|
||||
}
|
||||
|
||||
pub macro handle( $exc:ident: $($e:ident @ $t:ty => $b:expr),+ $(,)?) {
|
||||
alias_macro!(MatcherEnum: $($e),*);
|
||||
|
||||
match MatcherEnum::from( $exc ) {
|
||||
$(
|
||||
MatcherEnum::V0( $e ) if { let _: & $t = & $e; true } => {}
|
||||
),+,
|
||||
_error => _error?,
|
||||
}
|
||||
|
||||
}
|
||||
33
src/lib.rs
Normal file
33
src/lib.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#![no_std] #![feature(decl_macro, generic_const_exprs, freeze, try_trait_v2)]
|
||||
#![allow(incomplete_features, unused)]
|
||||
|
||||
pub(crate) mod config {
|
||||
|
||||
#[cfg(not(feature = "big_errors"))]
|
||||
pub const ERROR_SIZE: usize = 16;
|
||||
|
||||
#[cfg(feature = "big_errors")]
|
||||
pub const ERROR_SIZE: usize = 32;
|
||||
|
||||
pub const USER_ERROR_SIZE: usize = ERROR_SIZE - size_of::<u16>();
|
||||
}
|
||||
|
||||
// Re-Exports
|
||||
pub use exception::Exception;
|
||||
pub use helpers::*;
|
||||
|
||||
// Aliases
|
||||
pub type Result<T> = core::result::Result<T, Exception>;
|
||||
pub type Throwable<T> = Result<T>;
|
||||
|
||||
// Modules
|
||||
pub(crate) mod rand;
|
||||
pub(crate) mod user_types;
|
||||
pub(crate) mod exception;
|
||||
pub(crate) mod helpers;
|
||||
|
||||
// Re-Export Macros
|
||||
pub use user_types::new_exception;
|
||||
|
||||
|
||||
pub use user_types::UserException;
|
||||
29
src/rand.rs
Normal file
29
src/rand.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
#[track_caller]
|
||||
pub const fn random(seed: usize) -> u64 {
|
||||
|
||||
let value = match core::option_env!("SEED") {
|
||||
Some(_seed) => hash(_seed.as_bytes(), INITIAL_STATE),
|
||||
None => INITIAL_STATE,
|
||||
};
|
||||
|
||||
let value = hash(&seed.to_ne_bytes(), value);
|
||||
let caller = core::panic::Location::caller();
|
||||
let value = hash(&caller.file().as_bytes(), value);
|
||||
let value = hash(&caller.line().to_ne_bytes(), value);
|
||||
let value = hash(&caller.column().to_ne_bytes(), value);
|
||||
value
|
||||
}
|
||||
|
||||
|
||||
const INITIAL_STATE: u64 = 0xcbf29ce484222325;
|
||||
const PRIME: u64 = 0x100000001b3;
|
||||
const fn hash(bytes: &[u8], mut hash: u64) -> u64 {
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
hash = hash ^ bytes[i] as u64;
|
||||
hash = hash.wrapping_mul(PRIME);
|
||||
i += 1;
|
||||
}
|
||||
hash
|
||||
}
|
||||
104
src/user_types.rs
Normal file
104
src/user_types.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use core::ptr::drop_in_place;
|
||||
|
||||
use crate::Exception;
|
||||
use crate::rand::random;
|
||||
use crate::config::USER_ERROR_SIZE;
|
||||
|
||||
/// the start of the error table
|
||||
#[unsafe(link_section=".rdata$etypes.0")]
|
||||
pub static mut ERROR_TABLE_START: [u8;0] = [];
|
||||
|
||||
/// the error table is made up of pointers to these functions
|
||||
pub type DebugOrDrop = fn(*const(), Option<&mut core::fmt::Formatter>) -> core::fmt::Result;
|
||||
|
||||
/// Represents a type that is an exception
|
||||
pub trait UserException: Sized + core::marker::Freeze {
|
||||
|
||||
fn get_idx() -> u16;
|
||||
|
||||
#[inline(always)]
|
||||
fn convert(self) -> Exception {
|
||||
let mut except = Exception {
|
||||
typ: Self::get_idx(),
|
||||
data: [0u8;USER_ERROR_SIZE]
|
||||
};
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
&self as *const _ as *const u8,
|
||||
except.data.as_mut_ptr(),
|
||||
size_of::<Self>()
|
||||
);
|
||||
// we moved the data, don't drop it!
|
||||
core::mem::forget(self);
|
||||
}
|
||||
except
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "derive_debug")]
|
||||
pub fn debug_or_drop<T: UserException + Debug>(
|
||||
ptr: *const (), fmt: Option<&mut core::fmt::Formatter>
|
||||
) -> core::fmt::Result {
|
||||
// this is safe because the data is stored at offset 0 of our exception type.
|
||||
// so the alignment will always be correct (not shifted by 2)
|
||||
match fmt {
|
||||
// fmt is provided, write write the format...
|
||||
Some(fmt) => unsafe { &*(ptr as *const T)}.fmt(fmt),
|
||||
|
||||
// fmt was not provided, this function acts as the destructor
|
||||
None => unsafe {
|
||||
drop_in_place(ptr as *const T as *mut T);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "derive_debug"))]
|
||||
pub fn debug_or_drop<T: UserException>(
|
||||
ptr: *const (), fmt: Option<&mut core::fmt::Formatter>
|
||||
) -> core::fmt::Result {
|
||||
if fmt.is_none() {
|
||||
drop_in_place(ptr as *const T as *mut T);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub macro new_exception( $name:ident $(, $($arg:tt)*)? ) {
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct $name $( ( $( $arg )* ) )?;
|
||||
|
||||
const _: () = assert!(size_of::<$name>() <= USER_ERROR_SIZE, " is too large");
|
||||
impl UserException for $name {
|
||||
|
||||
#[inline(always)] fn get_idx() -> u16 {
|
||||
#[unsafe(link_section=".rdata$etypes.1")]
|
||||
static mut ERROR_TABLE_ENTRY: DebugOrDrop = debug_or_drop::<$name>;
|
||||
let mut idx: u64 = 0;
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"mov {o:}, offset {}@SECREL32 + {c}",
|
||||
"sub {o:}, offset {}@SECREL32 + {c}",
|
||||
sym ERROR_TABLE_ENTRY,
|
||||
sym ERROR_TABLE_START,
|
||||
o = out(reg) idx,
|
||||
c = const { random(1) }
|
||||
)
|
||||
}
|
||||
(idx >> 3) as _
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
62
tests/test.rs
Normal file
62
tests/test.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
e::new_exception!(GenericError, u32);
|
||||
e::new_exception!(OtherError, Box<String>);
|
||||
e::new_exception!(DropError, DropTest);
|
||||
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let e = test2();
|
||||
println!("{:?}", e);
|
||||
}
|
||||
|
||||
|
||||
fn test2() -> e::Result<()> {
|
||||
|
||||
for i in 0..3 {
|
||||
let exception = generic(i).unwrap_err();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
for i in 0..3 {
|
||||
let exception = generic(i).unwrap_err();
|
||||
|
||||
|
||||
match e::M2::from(exception) {
|
||||
|
||||
e::M2::V0(e) if { let _: &GenericError = &e; true } => {
|
||||
println!("Generic: {e:?}")
|
||||
}
|
||||
|
||||
e::M2::V1(e) => {
|
||||
let _: &OtherError = &e;
|
||||
println!("Other: {e:?}");
|
||||
}
|
||||
|
||||
error => error?,
|
||||
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
|
||||
fn generic(i: u32) -> e::Result<()> {
|
||||
match i {
|
||||
0 => Err(GenericError(0x69u32))?,
|
||||
1 => Err(OtherError(Box::new("Hello World".to_string())))?,
|
||||
2 => Err(DropError(DropTest))?,
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DropTest;
|
||||
impl Drop for DropTest {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropped @ {self:p}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user