Where was I?

This commit is contained in:
Michael Pfaff 2022-07-21 20:39:47 -04:00
commit fdb14c22ec
Signed by: michael
GPG Key ID: CF402C4A012AA9D4
7 changed files with 329 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/Cargo.lock

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "how"
version = "0.2.0"
edition = "2021"
[features]
default = []
backtrace = []
[dependencies]

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# How
A seriously contextual error library that focuses on how you got there. Designed to make debugging parser logic easier, How enables you to concisely capture any and all context that could possibly have contributed to an error.

107
src/context.rs Normal file
View File

@ -0,0 +1,107 @@
use std::borrow::Cow;
use crate::{Report, ReportFmt};
#[derive(Debug, Clone)]
pub struct Context(ContextInner);
impl Context {
pub fn chain(mut self, other: impl IntoContext) -> Self {
if let ContextInner::Compound(ref mut vec) = self.0 {
vec.push(other.into_context());
} else {
self = Context(ContextInner::Compound(vec![self, other.into_context()]))
}
self
}
}
impl Report for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, opts: &ReportFmt) -> std::fmt::Result {
use std::fmt::Write;
match self.0 {
ContextInner::String(ref s) => s.fmt(f, opts)?,
ContextInner::Compound(ref ctxs) => {
let mut opts = *opts;
for ctx in ctxs {
ctx.fmt(f, &opts)?;
f.write_char('\n')?;
opts = opts.next();
}
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
enum ContextInner {
String(Cow<'static, str>),
Compound(Vec<Context>),
}
pub trait IntoContext {
fn into_context(self) -> Context;
#[inline]
fn chain(self, other: impl IntoContext) -> Context
where
Self: Sized,
{
self.into_context().chain(other)
}
}
impl IntoContext for Context {
#[inline(always)]
fn into_context(self) -> Context {
self
}
}
impl IntoContext for String {
#[inline]
fn into_context(self) -> Context {
Context(ContextInner::String(self.into()))
}
}
impl IntoContext for &'static str {
#[inline]
fn into_context(self) -> Context {
Context(ContextInner::String(self.into()))
}
}
impl IntoContext for Cow<'static, str> {
#[inline]
fn into_context(self) -> Context {
Context(ContextInner::String(self))
}
}
impl<C, F> IntoContext for F
where
C: IntoContext,
F: FnOnce() -> C,
{
#[inline(always)]
fn into_context(self) -> Context {
self().into_context()
}
}
pub trait ToContext {
fn to_context(&self) -> Context;
}
impl<C, F> ToContext for F
where
C: IntoContext,
F: Fn() -> C,
{
#[inline(always)]
fn to_context(&self) -> Context {
self().into_context()
}
}

33
src/explain.rs Normal file
View File

@ -0,0 +1,33 @@
use crate::{How, IntoContext};
pub trait Explain: Sized {
type T;
fn explained(self) -> Result<Self::T, How>;
fn context(self, context: impl IntoContext) -> Result<Self::T, How> {
self.explained()
.context(context)
}
}
impl<T, E> Explain for Result<T, E> where E: std::error::Error {
type T = T;
fn explained(self) -> Result<Self::T, How> {
self.map_err(Into::into)
}
}
impl<T> Explain for Result<T, How> {
type T = T;
fn explained(self) -> Result<Self::T, How> {
self
}
#[inline(always)]
fn context(self, context: impl IntoContext) -> Result<Self::T, How> {
self.map_err(|e| e.context(context))
}
}

87
src/lib.rs Normal file
View File

@ -0,0 +1,87 @@
#![doc = include_str!("README.md")]
#![cfg_attr(feature = "backtrace", feature(backtrace))]
mod context;
pub use context::{Context, IntoContext, ToContext};
mod report;
pub use report::{Report, ReportFmt};
use report::{report_write, Indentation};
mod explain;
pub use explain::Explain;
pub type Result<T, E = How> = std::result::Result<T, E>;
/// Does not implement [`std::error::Error`] to allow a [`From`] implementation for all other error types.
#[derive(Debug)]
pub struct How {
/// When true, the error will cause branchers to abort.
classified: bool,
context: Vec<Context>,
#[cfg(feature = "backtrace")]
backtrace: std::backtrace::Backtrace,
}
impl How {
pub fn new(context: impl IntoContext) -> Self {
Self {
classified: false,
context: vec![context.into_context()],
#[cfg(feature = "backtrace")]
backtrace: std::backtrace::Backtrace::capture(),
}
}
pub fn clone_without_backtrace(&self) -> Self {
Self {
classified: self.classified,
context: self.context.clone(),
#[cfg(feature = "backtrace")]
backtrace: std::backtrace::Backtrace::disabled(),
}
}
#[inline]
pub const fn classified(mut self) -> Self {
self.classified = true;
self
}
pub fn context(mut self, context: impl IntoContext) -> Self {
self.context.push(context.into_context());
self
}
#[inline]
pub const fn is_classified(&self) -> bool {
self.classified
}
}
impl std::fmt::Display for How {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut opts = ReportFmt::default().indent_first(false);
report_write!(f, &opts, "Parsing failed")?;
for context in self.context.iter().rev() {
write!(f, "\n{}└ ", Indentation(opts.indentation()))?;
context.fmt(f, &opts)?;
opts = opts.indent();
}
#[cfg(feature = "backtrace")]
{
write!(f, "\n{}", self.backtrace)?;
};
Ok(())
}
}
impl<E> From<E> for How
where
E: std::error::Error,
{
fn from(value: E) -> Self {
Self::new(value.to_string())
}
}

87
src/report.rs Normal file
View File

@ -0,0 +1,87 @@
#[derive(Debug, Clone, Copy)]
pub struct ReportFmt {
indentation: usize,
is_first: bool,
indent_first: bool,
}
impl ReportFmt {
#[inline]
pub fn indent(mut self) -> Self {
self.indentation += 1;
self
}
#[inline]
pub fn next(mut self) -> Self {
self.is_first = false;
self
}
#[inline]
pub fn indent_first(mut self, indent_first: bool) -> Self {
self.indent_first = indent_first;
self
}
/// Returns the amount of indentation.
#[inline]
pub fn indentation(&self) -> usize {
self.indentation
}
#[inline]
pub fn should_indent(&self) -> bool {
self.indent_first || !self.is_first
}
}
impl Default for ReportFmt {
#[inline]
fn default() -> Self {
Self {
indentation: 0,
indent_first: true,
is_first: true,
}
}
}
macro_rules! report_write {
($f:expr, $opts:expr, $msg:literal$(, $($tt:tt)+)?) => {
<std::fmt::Arguments<'_> as $crate::Report>::fmt(&format_args!($msg$(, $($tt)+)?), $f, $opts)
};
}
pub(crate) use report_write;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Indentation(pub usize);
impl std::fmt::Display for Indentation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
for _ in 0..self.0 {
f.write_char(' ')?;
}
Ok(())
}
}
/// A more flexible formatting type that is a cross between [`std::fmt::Debug`] and
/// [`std::fmt::Display`].
pub trait Report {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, opts: &ReportFmt) -> std::fmt::Result;
}
impl<T> Report for T
where
T: std::fmt::Display,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, opts: &ReportFmt) -> std::fmt::Result {
if opts.should_indent() {
write!(f, "{}", Indentation(opts.indentation()))?;
}
<T as std::fmt::Display>::fmt(self, f)?;
Ok(())
}
}