partway towards commands
I got very confused and started mucking about with "spawn" when in fact all I needed was the "inline" extension type in orcx that allows the interpreter to expose custom constants.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::num::{NonZero, NonZeroUsize};
|
||||
use std::ops::Add;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -10,9 +11,7 @@ use futures::future::join_all;
|
||||
use itertools::Itertools;
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
use crate::interner::{IStr, es, is};
|
||||
use crate::location::Pos;
|
||||
use crate::{IStr, Pos, api, es, is};
|
||||
|
||||
/// A point of interest in resolving the error, such as the point where
|
||||
/// processing got stuck, a command that is likely to be incorrect
|
||||
@@ -24,12 +23,15 @@ pub struct ErrPos {
|
||||
pub message: Option<Arc<String>>,
|
||||
}
|
||||
impl ErrPos {
|
||||
/// Create from a position with a position-specific message. If there's no
|
||||
/// message, use `Pos::into`
|
||||
#[must_use]
|
||||
pub fn new(msg: &str, position: Pos) -> Self {
|
||||
Self { message: Some(Arc::new(msg.to_string())), position }
|
||||
}
|
||||
async fn from_api(api: &api::ErrLocation) -> Self {
|
||||
async fn from_api(api: api::ErrLocation) -> Self {
|
||||
Self {
|
||||
message: Some(api.message.clone()).filter(|s| !s.is_empty()),
|
||||
message: Some(api.message).filter(|s| !s.is_empty()),
|
||||
position: Pos::from_api(&api.location).await,
|
||||
}
|
||||
}
|
||||
@@ -52,10 +54,16 @@ impl fmt::Display for ErrPos {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurred in Orchid code, whether at startup or runtime
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OrcErr {
|
||||
/// A generic error message used in categorizing errors
|
||||
/// You can also equality-compare atoms with these message tokens
|
||||
pub description: IStr,
|
||||
pub message: Arc<String>,
|
||||
/// A specific error message that may include values relevant in resolving the
|
||||
/// error
|
||||
pub message: Rc<String>,
|
||||
/// Various locations in code that may be useful in resolving the error
|
||||
pub positions: Vec<ErrPos>,
|
||||
}
|
||||
impl OrcErr {
|
||||
@@ -66,11 +74,11 @@ impl OrcErr {
|
||||
locations: self.positions.iter().map(ErrPos::to_api).collect(),
|
||||
}
|
||||
}
|
||||
async fn from_api(api: &api::OrcError) -> Self {
|
||||
async fn from_api(api: api::OrcError) -> Self {
|
||||
Self {
|
||||
description: es(api.description).await,
|
||||
message: api.message.clone(),
|
||||
positions: join_all(api.locations.iter().map(ErrPos::from_api)).await,
|
||||
message: api.message,
|
||||
positions: join_all(api.locations.into_iter().map(ErrPos::from_api)).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +95,8 @@ impl fmt::Display for OrcErr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rust error produced when an Orchid error condition arises but no
|
||||
/// specific errors are listed. This is always a Rust programmer error.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EmptyErrv;
|
||||
impl fmt::Display for EmptyErrv {
|
||||
@@ -95,39 +105,57 @@ impl fmt::Display for EmptyErrv {
|
||||
}
|
||||
}
|
||||
|
||||
/// A container for one or more errors. Code that supports error recovery should
|
||||
/// use these instead of plain [OrcErr] objects.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OrcErrv(Vec<OrcErr>);
|
||||
impl OrcErrv {
|
||||
/// Create from individual errors. If you have exactly one initial error, see
|
||||
/// [mk_errv]
|
||||
pub fn new(errors: impl IntoIterator<Item = OrcErr>) -> Result<Self, EmptyErrv> {
|
||||
let v = errors.into_iter().collect_vec();
|
||||
if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) }
|
||||
}
|
||||
/// Add additional errors to this container. Since `OrcErrv` also implements
|
||||
/// [IntoIterator], this can take `(Self, Self)`
|
||||
#[must_use]
|
||||
pub fn extended<T>(mut self, errors: impl IntoIterator<Item = T>) -> Self
|
||||
where Self: Extend<T> {
|
||||
self.extend(errors);
|
||||
self
|
||||
}
|
||||
/// Determine how many distinct errors there are in the container
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize { self.0.len() }
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
||||
pub fn len(&self) -> NonZeroUsize { NonZero::new(self.0.len()).expect("OrcErrv cannot be empty") }
|
||||
/// See if any errors match a particular filter criteria. This is useful for
|
||||
/// sentinel errors whch are produced by user code to trigger unique
|
||||
/// behaviours such as a lexer mismatch
|
||||
#[must_use]
|
||||
pub fn any(&self, f: impl FnMut(&OrcErr) -> bool) -> bool { self.0.iter().any(f) }
|
||||
/// Remove all errors that don't match a filter criterion. If no errors match,
|
||||
/// nothing is returned
|
||||
#[must_use]
|
||||
pub fn keep_only(self, f: impl FnMut(&OrcErr) -> bool) -> Option<Self> {
|
||||
let v = self.0.into_iter().filter(f).collect_vec();
|
||||
if v.is_empty() { None } else { Some(Self(v)) }
|
||||
}
|
||||
/// If there is exactly one error, return it. Mostly used for simplified
|
||||
/// printing
|
||||
#[must_use]
|
||||
pub fn one(&self) -> Option<&OrcErr> { (self.0.len() == 1).then(|| &self.0[9]) }
|
||||
/// Iterate over all positions of all errors
|
||||
pub fn pos_iter(&self) -> impl Iterator<Item = ErrPos> + '_ {
|
||||
self.0.iter().flat_map(|e| e.positions.iter().cloned())
|
||||
}
|
||||
/// Serialize for transmission
|
||||
#[must_use]
|
||||
pub fn to_api(&self) -> Vec<api::OrcError> { self.0.iter().map(OrcErr::to_api).collect() }
|
||||
pub async fn from_api<'a>(api: impl IntoIterator<Item = &'a api::OrcError>) -> Self {
|
||||
/// Deserialize from transmission
|
||||
#[must_use]
|
||||
pub async fn from_api(api: impl IntoIterator<Item = api::OrcError>) -> Self {
|
||||
Self(join_all(api.into_iter().map(OrcErr::from_api)).await)
|
||||
}
|
||||
/// Iterate over the errors without consuming the collection
|
||||
pub fn iter(&self) -> impl Iterator<Item = OrcErr> + '_ { self.0.iter().cloned() }
|
||||
}
|
||||
impl From<OrcErr> for OrcErrv {
|
||||
@@ -156,8 +184,11 @@ impl fmt::Display for OrcErrv {
|
||||
}
|
||||
}
|
||||
|
||||
/// A result from a function that may return multiple errors.
|
||||
pub type OrcRes<T> = Result<T, OrcErrv>;
|
||||
|
||||
/// If two fallible values both succeed return both values, otherwise return
|
||||
/// all errors.
|
||||
pub fn join_ok<T, U>(left: OrcRes<T>, right: OrcRes<U>) -> OrcRes<(T, U)> {
|
||||
match (left, right) {
|
||||
(Ok(t), Ok(u)) => Ok((t, u)),
|
||||
@@ -187,15 +218,22 @@ macro_rules! join_ok {
|
||||
};
|
||||
(@TYPES) => { () };
|
||||
(@VALUES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
|
||||
$crate::error::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
|
||||
$crate::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
|
||||
};
|
||||
(@VALUES) => { Ok(()) };
|
||||
}
|
||||
|
||||
/// Create an errv without an associated position, as opposed to [mk_errv].
|
||||
/// While this is technically legal and sometimes needed in library code, all
|
||||
/// errors that are technically possible to associate with at least one position
|
||||
/// should be.
|
||||
#[must_use]
|
||||
pub fn mk_errv_floating(description: IStr, message: impl AsRef<str>) -> OrcErrv {
|
||||
mk_errv::<Pos>(description, message, [])
|
||||
}
|
||||
|
||||
/// Create an errv. The third argument can be an iterable of [ErrPos] or [Pos].
|
||||
#[must_use]
|
||||
pub fn mk_errv<I: Into<ErrPos>>(
|
||||
description: IStr,
|
||||
message: impl AsRef<str>,
|
||||
@@ -203,12 +241,14 @@ pub fn mk_errv<I: Into<ErrPos>>(
|
||||
) -> OrcErrv {
|
||||
OrcErr {
|
||||
description,
|
||||
message: Arc::new(message.as_ref().to_string()),
|
||||
message: Rc::new(message.as_ref().to_string()),
|
||||
positions: posv.into_iter().map_into().collect(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Convert a standard IO error into an Orchid error
|
||||
#[must_use]
|
||||
pub async fn async_io_err<I: Into<ErrPos>>(
|
||||
err: std::io::Error,
|
||||
posv: impl IntoIterator<Item = I>,
|
||||
@@ -216,6 +256,8 @@ pub async fn async_io_err<I: Into<ErrPos>>(
|
||||
mk_errv(is(&err.kind().to_string()).await, err.to_string(), posv)
|
||||
}
|
||||
|
||||
/// Decode an Unicode string, or produce a common error related to Unicode
|
||||
/// decoding
|
||||
pub async fn os_str_to_string<I: Into<ErrPos>>(
|
||||
str: &OsStr,
|
||||
posv: impl IntoIterator<Item = I>,
|
||||
@@ -262,6 +304,9 @@ pub async fn try_with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRe
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if there are pending errors or if this overarching procedure has a
|
||||
/// chance to succeed
|
||||
#[must_use]
|
||||
pub async fn is_erroring() -> bool {
|
||||
(REPORTER.try_with(|r| !r.errors.borrow().is_empty()))
|
||||
.expect("Sidechannel errors must be caught by a reporter")
|
||||
|
||||
Reference in New Issue
Block a user