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:
2026-03-13 16:48:42 +01:00
parent cdcca694c5
commit 09cfcb1839
146 changed files with 3582 additions and 2822 deletions

View File

@@ -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")