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:
@@ -6,9 +6,9 @@ edition = "2024"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-event = "0.2.1"
|
||||
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
||||
async-once-cell = "0.5.4"
|
||||
bound = "0.6.0"
|
||||
derive_destructure = "1.0.0"
|
||||
dyn-clone = "1.0.20"
|
||||
futures = { version = "0.3.31", default-features = false, features = [
|
||||
@@ -27,6 +27,7 @@ once_cell = "1.21.3"
|
||||
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
||||
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
||||
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
||||
orchid-async-utils = { version = "0.1.0", path = "../orchid-async-utils" }
|
||||
orchid-base = { version = "0.1.0", path = "../orchid-base" }
|
||||
ordered-float = "5.1.0"
|
||||
pastey = "0.2.1"
|
||||
|
||||
@@ -4,87 +4,82 @@ use std::fmt::{self, Debug};
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::num::NonZeroU32;
|
||||
use std::num::{NonZero, NonZeroU32, NonZeroU64};
|
||||
use std::ops::Deref;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use dyn_clone::{DynClone, clone_box};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::stream::LocalBoxStream;
|
||||
use futures::{AsyncWrite, FutureExt, StreamExt, stream};
|
||||
use orchid_api_derive::Coding;
|
||||
use orchid_api_traits::{Coding, Decode, InHierarchy, Request, UnderRoot, enc_vec};
|
||||
use orchid_base::error::{OrcErrv, OrcRes, mk_errv, mk_errv_floating};
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, fmt, take_first};
|
||||
use orchid_base::interner::is;
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::reqnot::{Receipt, ReqHandle, ReqReader, ReqReaderExt};
|
||||
use orchid_base::{
|
||||
FmtCtx, FmtUnit, Format, IStr, OrcErrv, Pos, Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym,
|
||||
fmt, is, mk_errv, mk_errv_floating, take_first,
|
||||
};
|
||||
use trait_set::trait_set;
|
||||
|
||||
use crate::api;
|
||||
use crate::atom_owned::{OwnedAtom, get_obj_store};
|
||||
use crate::conv::ToExpr;
|
||||
use crate::entrypoint::request;
|
||||
// use crate::error::{ProjectError, ProjectResult};
|
||||
use crate::expr::{Expr, ExprData, ExprHandle, ExprKind};
|
||||
use crate::gen_expr::GExpr;
|
||||
use crate::system::{DynSystemCard, atom_by_idx, atom_info_for, cted, downcast_atom};
|
||||
use crate::gen_expr::{GExpr, IntoGExprStream};
|
||||
use crate::system::{DynSystemCardExt, cted, sys_id};
|
||||
|
||||
/// Every atom managed via this system starts with an ID into the type table
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
|
||||
pub struct AtomTypeId(pub NonZeroU32);
|
||||
|
||||
pub trait AtomCard: 'static + Sized {
|
||||
type Data: Clone + Coding + Sized;
|
||||
}
|
||||
|
||||
pub trait AtomicVariant {}
|
||||
|
||||
/// A value managed by Orchid. The type should also be registered in the
|
||||
/// [crate::SystemCard] through [AtomicFeatures::ops] which is provided
|
||||
/// indirectly by either [crate::OwnedAtom] or [crate::ThinAtom]
|
||||
pub trait Atomic: 'static + Sized {
|
||||
/// Either [crate::OwnedVariant] or [crate::ThinVariant] depending on whether
|
||||
/// the value implements [crate::OwnedAtom] or [crate::ThinAtom]
|
||||
type Variant: AtomicVariant;
|
||||
/// Serializable data that gets sent inside the atom to other systems that
|
||||
/// depend on this system. Methods on this value are directly accessible
|
||||
/// through [TAtom], and this data can also be used for optimized public
|
||||
/// functions. The serialized form should have a reasonable length to avoid
|
||||
/// overburdening the protocol.
|
||||
type Data: Clone + Coding + Sized + 'static;
|
||||
/// Register handlers for IPC calls. If this atom implements [Supports], you
|
||||
/// should register your implementations here. If this atom doesn't
|
||||
/// participate in IPC at all, the default implementation is fine
|
||||
fn reg_reqs() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
|
||||
}
|
||||
impl<A: Atomic> AtomCard for A {
|
||||
type Data = <Self as Atomic>::Data;
|
||||
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
|
||||
}
|
||||
|
||||
/// Shared interface of all atom types created in this library for use by the
|
||||
/// library that defines them. This is provided by [Atomic] and either
|
||||
/// [crate::OwnedAtom] or [crate::ThinAtom]
|
||||
pub trait AtomicFeatures: Atomic {
|
||||
/// Convert a value of this atom inside the defining system into a function
|
||||
/// that will perform registrations and serialization
|
||||
#[allow(private_interfaces)]
|
||||
fn factory(self) -> AtomFactory;
|
||||
type Info: AtomDynfo;
|
||||
fn info() -> Self::Info;
|
||||
fn dynfo() -> Box<dyn AtomDynfo>;
|
||||
/// Expose all operations that can be performed on an instance of this type in
|
||||
/// an instanceless vtable. This vtable must be registered by the
|
||||
/// [crate::System].
|
||||
fn ops() -> Box<dyn AtomOps>;
|
||||
}
|
||||
pub trait ToAtom {
|
||||
fn to_atom_factory(self) -> AtomFactory;
|
||||
}
|
||||
impl<A: AtomicFeatures> ToAtom for A {
|
||||
fn to_atom_factory(self) -> AtomFactory { self.factory() }
|
||||
}
|
||||
impl ToAtom for AtomFactory {
|
||||
fn to_atom_factory(self) -> AtomFactory { self }
|
||||
}
|
||||
pub trait AtomicFeaturesImpl<Variant: AtomicVariant> {
|
||||
pub(crate) trait AtomicFeaturesImpl<Variant: AtomicVariant> {
|
||||
fn _factory(self) -> AtomFactory;
|
||||
type _Info: AtomDynfo;
|
||||
type _Info: AtomOps;
|
||||
fn _info() -> Self::_Info;
|
||||
}
|
||||
impl<A: Atomic + AtomicFeaturesImpl<A::Variant>> AtomicFeatures for A {
|
||||
#[allow(private_interfaces)]
|
||||
fn factory(self) -> AtomFactory { self._factory() }
|
||||
type Info = <Self as AtomicFeaturesImpl<A::Variant>>::_Info;
|
||||
fn info() -> Self::Info { Self::_info() }
|
||||
fn dynfo() -> Box<dyn AtomDynfo> { Box::new(Self::info()) }
|
||||
}
|
||||
|
||||
pub fn get_info<A: AtomCard>(
|
||||
sys: &(impl DynSystemCard + ?Sized),
|
||||
) -> (AtomTypeId, Box<dyn AtomDynfo>) {
|
||||
atom_info_for(sys, TypeId::of::<A>()).unwrap_or_else(|| {
|
||||
panic!("Atom {} not associated with system {}", type_name::<A>(), sys.name())
|
||||
})
|
||||
fn ops() -> Box<dyn AtomOps> { Box::new(Self::_info()) }
|
||||
}
|
||||
|
||||
/// A reference to a value of some [Atomic] type. This owns an [Expr]
|
||||
#[derive(Clone)]
|
||||
pub struct ForeignAtom {
|
||||
pub(crate) expr: Rc<ExprHandle>,
|
||||
@@ -92,7 +87,9 @@ pub struct ForeignAtom {
|
||||
pub(crate) pos: Pos,
|
||||
}
|
||||
impl ForeignAtom {
|
||||
/// Obtain the position in code of the expression
|
||||
pub fn pos(&self) -> Pos { self.pos.clone() }
|
||||
/// Obtain the [Expr]
|
||||
pub fn ex(self) -> Expr {
|
||||
let (handle, pos) = (self.expr.clone(), self.pos.clone());
|
||||
let data = ExprData { pos, kind: ExprKind::Atom(ForeignAtom { ..self }) };
|
||||
@@ -101,10 +98,9 @@ impl ForeignAtom {
|
||||
pub(crate) fn new(handle: Rc<ExprHandle>, atom: api::Atom, pos: Pos) -> Self {
|
||||
ForeignAtom { atom, expr: handle, pos }
|
||||
}
|
||||
pub async fn request<R: Request + UnderRoot<Root: AtomMethod>>(
|
||||
&self,
|
||||
r: R,
|
||||
) -> Option<R::Response> {
|
||||
/// Call an IPC method. If the type does not support the given method type,
|
||||
/// this function returns [None]
|
||||
pub async fn call<R: Request + UnderRoot<Root: AtomMethod>>(&self, r: R) -> Option<R::Response> {
|
||||
let rep = (request(api::Fwd(
|
||||
self.atom.clone(),
|
||||
Sym::parse(<R as UnderRoot>::Root::NAME).await.unwrap().tok().to_api(),
|
||||
@@ -113,8 +109,33 @@ impl ForeignAtom {
|
||||
.await?;
|
||||
Some(R::Response::decode_slice(&mut &rep[..]))
|
||||
}
|
||||
pub async fn downcast<T: AtomicFeatures>(self) -> Result<TAtom<T>, NotTypAtom> {
|
||||
TAtom::downcast(self.ex().handle()).await
|
||||
/// Attempt to downcast this value to a concrete atom type
|
||||
pub fn downcast<A: Atomic>(self) -> Result<TAtom<A>, NotTypAtom> {
|
||||
let mut data = &self.atom.data.0[..];
|
||||
let value = AtomTypeId::decode_slice(&mut data);
|
||||
if cfg!(debug_assertions) {
|
||||
let cted = cted();
|
||||
let own_inst = cted.inst();
|
||||
let owner_id = self.atom.owner;
|
||||
let typ = type_name::<A>();
|
||||
let owner = if sys_id() == owner_id {
|
||||
own_inst.card()
|
||||
} else {
|
||||
(cted.deps().find(|s| s.id() == self.atom.owner))
|
||||
.ok_or_else(|| NotTypAtom { expr: self.clone().ex(), pos: self.pos(), typ })?
|
||||
.get_card()
|
||||
};
|
||||
let Some(ops) = owner.ops_by_atid(value) else {
|
||||
panic!("{value:?} does not refer to an atom in {owner_id:?} when downcasting {typ}");
|
||||
};
|
||||
if ops.tid() != TypeId::of::<A>() {
|
||||
panic!(
|
||||
"{value:?} of {owner_id:?} refers to a type other than {typ}. System version mismatch?"
|
||||
)
|
||||
}
|
||||
}
|
||||
let value = A::Data::decode_slice(&mut data);
|
||||
Ok(TAtom { value, untyped: self })
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ForeignAtom {
|
||||
@@ -139,13 +160,14 @@ impl ToExpr for ForeignAtom {
|
||||
pub struct NotTypAtom {
|
||||
pub pos: Pos,
|
||||
pub expr: Expr,
|
||||
pub typ: Box<dyn AtomDynfo>,
|
||||
pub typ: &'static str,
|
||||
}
|
||||
impl NotTypAtom {
|
||||
/// Convert to a generic Orchid error
|
||||
pub async fn mk_err(&self) -> OrcErrv {
|
||||
mk_errv(
|
||||
is("Not the expected type").await,
|
||||
format!("The expression {} is not a {}", fmt(&self.expr).await, self.typ.name()),
|
||||
format!("The expression {} is not a {}", fmt(&self.expr).await, self.typ),
|
||||
[self.pos.clone()],
|
||||
)
|
||||
}
|
||||
@@ -155,15 +177,21 @@ impl Debug for NotTypAtom {
|
||||
f.debug_struct("NotTypAtom")
|
||||
.field("pos", &self.pos)
|
||||
.field("expr", &self.expr)
|
||||
.field("typ.name", &self.typ.name())
|
||||
.field("typ", &self.typ)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// An IPC request associated with an atom. This type should either implement
|
||||
/// [Request] or be the root of a [orchid_api_derive::Hierarchy] the leaves of
|
||||
/// which implement [Request].
|
||||
pub trait AtomMethod: Coding + InHierarchy {
|
||||
const NAME: &str;
|
||||
}
|
||||
pub trait Supports<M: AtomMethod>: AtomCard {
|
||||
|
||||
/// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be
|
||||
/// registered in [Atomic::reg_methods]
|
||||
pub trait Supports<M: AtomMethod>: Atomic {
|
||||
fn handle<'a>(
|
||||
&self,
|
||||
hand: Box<dyn ReqHandle<'a> + '_>,
|
||||
@@ -192,12 +220,17 @@ impl<M: AtomMethod, A: Supports<M>> HandleAtomMethod<A> for AtomMethodHandler<M,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MethodSetBuilder<A: AtomCard> {
|
||||
/// A collection of [Supports] impls for an [Atomic]. If a [Supports]
|
||||
/// impl is not added to the method set, it will not be recognized. Note that
|
||||
/// the [Supports] implementors must be registered, which are not necessarily
|
||||
/// the same as the [Request] implementors
|
||||
pub struct MethodSetBuilder<A: Atomic> {
|
||||
handlers: Vec<(&'static str, Rc<dyn HandleAtomMethod<A>>)>,
|
||||
}
|
||||
impl<A: AtomCard> MethodSetBuilder<A> {
|
||||
impl<A: Atomic> MethodSetBuilder<A> {
|
||||
pub fn new() -> Self { Self { handlers: vec![] } }
|
||||
|
||||
/// Add an [AtomMethod]
|
||||
pub fn handle<M: AtomMethod>(mut self) -> Self
|
||||
where A: Supports<M> {
|
||||
assert!(!M::NAME.is_empty(), "AtomMethod::NAME cannoot be empty");
|
||||
@@ -205,7 +238,7 @@ impl<A: AtomCard> MethodSetBuilder<A> {
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn pack(&self) -> MethodSet<A> {
|
||||
pub(crate) async fn pack(&self) -> MethodSet<A> {
|
||||
MethodSet {
|
||||
handlers: stream::iter(self.handlers.iter())
|
||||
.then(async |(k, v)| (Sym::parse(k).await.unwrap(), v.clone()))
|
||||
@@ -215,10 +248,10 @@ impl<A: AtomCard> MethodSetBuilder<A> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MethodSet<A: AtomCard> {
|
||||
pub(crate) struct MethodSet<A: Atomic> {
|
||||
handlers: HashMap<Sym, Rc<dyn HandleAtomMethod<A>>>,
|
||||
}
|
||||
impl<A: AtomCard> MethodSet<A> {
|
||||
impl<A: Atomic> MethodSet<A> {
|
||||
pub(crate) async fn dispatch<'a>(
|
||||
&self,
|
||||
atom: &'_ A,
|
||||
@@ -235,29 +268,45 @@ impl<A: AtomCard> MethodSet<A> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AtomCard> Default for MethodSetBuilder<A> {
|
||||
impl<A: Atomic> Default for MethodSetBuilder<A> {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
/// A handle to a value defined by this or another system. This owns an [Expr]
|
||||
#[derive(Clone)]
|
||||
pub struct TAtom<A: AtomicFeatures> {
|
||||
pub struct TAtom<A: Atomic> {
|
||||
pub untyped: ForeignAtom,
|
||||
pub value: A::Data,
|
||||
}
|
||||
impl<A: AtomicFeatures> TAtom<A> {
|
||||
impl<A: Atomic> TAtom<A> {
|
||||
/// Obtain the underlying [Expr]
|
||||
pub fn ex(&self) -> Expr { self.untyped.clone().ex() }
|
||||
/// Obtain the position in code associated with the atom
|
||||
pub fn pos(&self) -> Pos { self.untyped.pos() }
|
||||
/// Produce from an [ExprHandle] directly
|
||||
pub async fn downcast(expr: Rc<ExprHandle>) -> Result<Self, NotTypAtom> {
|
||||
match Expr::from_handle(expr).atom().await {
|
||||
Err(expr) =>
|
||||
Err(NotTypAtom { pos: expr.data().await.pos.clone(), expr, typ: Box::new(A::info()) }),
|
||||
Ok(atm) => match downcast_atom::<A>(atm).await {
|
||||
Ok(tatom) => Ok(tatom),
|
||||
Err(fa) => Err(NotTypAtom { pos: fa.pos.clone(), expr: fa.ex(), typ: Box::new(A::info()) }),
|
||||
},
|
||||
Err(NotTypAtom { pos: expr.data().await.pos.clone(), expr, typ: type_name::<A>() }),
|
||||
Ok(atm) => atm.downcast(),
|
||||
}
|
||||
}
|
||||
pub async fn request<R: Request + UnderRoot<Root: AtomMethod>>(&self, req: R) -> R::Response
|
||||
/// Find the instance associated with a [TAtom] that we own
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if we don't actually own this atom
|
||||
pub async fn own(&self) -> A
|
||||
where A: OwnedAtom {
|
||||
let g = get_obj_store().objects.read().await;
|
||||
let atom_id = self.untyped.atom.drop.expect("Owned atoms always have a drop ID");
|
||||
let dyn_atom =
|
||||
g.get(&atom_id).expect("Atom ID invalid; atom type probably not owned by this crate");
|
||||
dyn_atom.as_any_ref().downcast_ref().cloned().expect("The ID should imply a type as well")
|
||||
}
|
||||
/// Call an IPC method on the value. Since we know the type, unlike
|
||||
/// [ForeignAtom::call], we can ensure that the callee recognizes this method
|
||||
pub async fn call<R: Request + UnderRoot<Root: AtomMethod>>(&self, req: R) -> R::Response
|
||||
where A: Supports<<R as UnderRoot>::Root> {
|
||||
R::Response::decode_slice(
|
||||
&mut &(request(api::Fwd(
|
||||
@@ -283,9 +332,82 @@ impl<A: AtomicFeatures> Format for TAtom<A> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AtomCtx<'a>(pub &'a [u8], pub Option<api::AtomId>);
|
||||
pub(crate) struct AtomCtx<'a>(pub &'a [u8], pub Option<api::AtomId>);
|
||||
|
||||
pub trait AtomDynfo: 'static {
|
||||
pub enum Next {
|
||||
ExitSuccess,
|
||||
Continue(Continuation),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Continuation {
|
||||
immediate: Vec<LocalBoxStream<'static, GExpr>>,
|
||||
delayed: Vec<(NonZeroU64, LocalBoxStream<'static, GExpr>)>,
|
||||
}
|
||||
impl Continuation {
|
||||
pub fn immediate<T: IntoGExprStream + 'static>(mut self, expr: T) -> Self {
|
||||
self.immediate.push(Box::pin(expr.into_gexpr_stream()));
|
||||
self
|
||||
}
|
||||
pub fn schedule<T: IntoGExprStream + 'static>(mut self, delay: Duration, expr: T) -> Self {
|
||||
let delay = delay.as_millis().try_into().unwrap();
|
||||
let Some(nzdelay) = NonZero::new(delay) else { return self.immediate(expr) };
|
||||
self.delayed.push((nzdelay, Box::pin(expr.into_gexpr_stream())));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Continuation> for Next {
|
||||
fn from(value: Continuation) -> Self { Self::Continue(value) }
|
||||
}
|
||||
|
||||
impl From<Continuation> for CmdResult {
|
||||
fn from(value: Continuation) -> Self { Ok(Next::Continue(value)) }
|
||||
}
|
||||
|
||||
pub enum CmdError {
|
||||
Orc(OrcErrv),
|
||||
FatalError,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FatalError;
|
||||
impl From<FatalError> for CmdError {
|
||||
fn from(FatalError: FatalError) -> Self { Self::FatalError }
|
||||
}
|
||||
impl From<OrcErrv> for CmdError {
|
||||
fn from(value: OrcErrv) -> Self { Self::Orc(value) }
|
||||
}
|
||||
|
||||
pub(crate) async fn encode_command_result(
|
||||
result: Result<Next, CmdError>,
|
||||
) -> api::OrcResult<api::NextStep> {
|
||||
match result {
|
||||
Ok(Next::ExitSuccess) => Ok(api::NextStep::Exit { success: true }),
|
||||
Err(CmdError::FatalError) => Ok(api::NextStep::Exit { success: false }),
|
||||
Err(CmdError::Orc(errv)) => Err(errv.to_api()),
|
||||
Ok(Next::Continue(Continuation { immediate, delayed })) => Ok(api::NextStep::Continue {
|
||||
immediate: (stream::iter(immediate).flatten())
|
||||
.then(async |x| x.serialize().await)
|
||||
.collect()
|
||||
.await,
|
||||
delayed: stream::iter(delayed.into_iter().map(|(delay, expr)| {
|
||||
expr.then(move |expr| async move { (delay, expr.serialize().await) })
|
||||
}))
|
||||
.flatten()
|
||||
.collect()
|
||||
.await,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub type CmdResult = Result<Next, CmdError>;
|
||||
|
||||
/// A vtable-like type that collects operations defined by an [Atomic] without
|
||||
/// associating with an instance of that type. This must be registered in
|
||||
/// [crate::SystemCard]
|
||||
#[allow(private_interfaces)]
|
||||
pub trait AtomOps: 'static {
|
||||
fn tid(&self) -> TypeId;
|
||||
fn name(&self) -> &'static str;
|
||||
fn decode<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>>;
|
||||
@@ -298,25 +420,29 @@ pub trait AtomDynfo: 'static {
|
||||
key: Sym,
|
||||
req: Box<dyn ReqReader<'a> + 'a>,
|
||||
) -> LocalBoxFuture<'a, bool>;
|
||||
fn command<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>>;
|
||||
fn command<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, CmdResult>;
|
||||
fn serialize<'a, 'b: 'a>(
|
||||
&'a self,
|
||||
ctx: AtomCtx<'a>,
|
||||
write: Pin<&'b mut dyn AsyncWrite>,
|
||||
) -> LocalBoxFuture<'a, Option<Vec<Expr>>>;
|
||||
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom>;
|
||||
fn deserialize<'a>(
|
||||
&'a self,
|
||||
data: &'a [u8],
|
||||
refs: &'a [Expr],
|
||||
) -> LocalBoxFuture<'a, api::LocalAtom>;
|
||||
fn drop<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, ()>;
|
||||
}
|
||||
|
||||
trait_set! {
|
||||
pub trait AtomFactoryFn = FnOnce() -> LocalBoxFuture<'static, api::Atom> + DynClone;
|
||||
pub trait AtomFactoryFn = FnOnce() -> LocalBoxFuture<'static, api::LocalAtom> + DynClone;
|
||||
}
|
||||
pub struct AtomFactory(Box<dyn AtomFactoryFn>);
|
||||
pub(crate) struct AtomFactory(Box<dyn AtomFactoryFn>);
|
||||
impl AtomFactory {
|
||||
pub fn new(f: impl AsyncFnOnce() -> api::Atom + Clone + 'static) -> Self {
|
||||
pub fn new(f: impl AsyncFnOnce() -> api::LocalAtom + Clone + 'static) -> Self {
|
||||
Self(Box::new(|| f().boxed_local()))
|
||||
}
|
||||
pub async fn build(self) -> api::Atom { (self.0)().await }
|
||||
pub async fn build(self) -> api::LocalAtom { (self.0)().await }
|
||||
}
|
||||
impl Clone for AtomFactory {
|
||||
fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0)) }
|
||||
@@ -333,6 +459,7 @@ impl Format for AtomFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error produced when an atom can not be applied to a value as a function
|
||||
pub async fn err_not_callable(unit: &FmtUnit) -> OrcErrv {
|
||||
mk_errv_floating(
|
||||
is("This atom is not callable").await,
|
||||
@@ -340,6 +467,7 @@ pub async fn err_not_callable(unit: &FmtUnit) -> OrcErrv {
|
||||
)
|
||||
}
|
||||
|
||||
/// Error produced when an atom can not be the final value of the program
|
||||
pub async fn err_not_command(unit: &FmtUnit) -> OrcErrv {
|
||||
mk_errv_floating(
|
||||
is("This atom is not a command").await,
|
||||
@@ -347,11 +475,35 @@ pub async fn err_not_command(unit: &FmtUnit) -> OrcErrv {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn err_exit_success_msg() -> IStr { is("Early successful exit").await }
|
||||
pub(crate) async fn err_exit_failure_msg() -> IStr { is("Early failure exit").await }
|
||||
|
||||
/// Sentinel error returnable from [crate::OwnedAtom::command] or
|
||||
/// [crate::ThinAtom::command] to indicate that the program should exit with a
|
||||
/// success
|
||||
pub async fn err_exit_success() -> OrcErrv {
|
||||
mk_errv_floating(
|
||||
err_exit_success_msg().await,
|
||||
"Sentinel error indicating that the program should exit with a success.",
|
||||
)
|
||||
}
|
||||
|
||||
/// Sentinel error returnable from [crate::OwnedAtom::command] or
|
||||
/// [crate::ThinAtom::command] to indicate that the program should exit with a
|
||||
/// failure
|
||||
pub async fn err_exit_failure() -> OrcErrv {
|
||||
mk_errv_floating(
|
||||
err_exit_failure_msg().await,
|
||||
"Sentinel error indicating that the program should exit with a failure \
|
||||
but without raising an error.",
|
||||
)
|
||||
}
|
||||
|
||||
/// Read the type ID prefix from an atom, return type information and the rest
|
||||
/// of the data
|
||||
pub(crate) fn resolve_atom_type(atom: &api::Atom) -> (Box<dyn AtomDynfo>, AtomTypeId, &[u8]) {
|
||||
pub(crate) fn resolve_atom_type(atom: &api::Atom) -> (Box<dyn AtomOps>, AtomTypeId, &[u8]) {
|
||||
let mut data = &atom.data.0[..];
|
||||
let tid = AtomTypeId::decode_slice(&mut data);
|
||||
let atom_record = atom_by_idx(cted().inst().card(), tid).expect("Unrecognized atom type ID");
|
||||
(atom_record, tid, data)
|
||||
let atid = AtomTypeId::decode_slice(&mut data);
|
||||
let atom_record = cted().inst().card().ops_by_atid(atid).expect("Unrecognized atom type ID");
|
||||
(atom_record, atid, data)
|
||||
}
|
||||
|
||||
@@ -17,21 +17,20 @@ use itertools::Itertools;
|
||||
use memo_map::MemoMap;
|
||||
use never::Never;
|
||||
use orchid_api_traits::{Decode, Encode, enc_vec};
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::format::{FmtCtx, FmtCtxImpl, FmtUnit, Format, take_first};
|
||||
use orchid_base::logging::log;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::{FmtCtx, FmtCtxImpl, FmtUnit, Format, Sym, log, take_first};
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
use crate::atom::{
|
||||
AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
|
||||
MethodSetBuilder, TAtom, err_not_callable, err_not_command, get_info,
|
||||
AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
|
||||
MethodSetBuilder, err_not_callable, err_not_command,
|
||||
};
|
||||
use crate::conv::ToExpr;
|
||||
use crate::expr::Expr;
|
||||
use crate::gen_expr::{GExpr, bot};
|
||||
use crate::system::{cted, sys_id};
|
||||
use crate::system::{DynSystemCardExt, cted};
|
||||
use crate::{CmdError, CmdResult, api};
|
||||
|
||||
/// Value of [Atomic::Variant] for a type that implements [OwnedAtom]
|
||||
pub struct OwnedVariant;
|
||||
impl AtomicVariant for OwnedVariant {}
|
||||
impl<A: OwnedAtom + Atomic<Variant = OwnedVariant>> AtomicFeaturesImpl<OwnedVariant> for A {
|
||||
@@ -43,15 +42,15 @@ impl<A: OwnedAtom + Atomic<Variant = OwnedVariant>> AtomicFeaturesImpl<OwnedVari
|
||||
*id += 1;
|
||||
api::AtomId(NonZero::new(*id + 1).unwrap())
|
||||
};
|
||||
let (typ_id, _) = get_info::<A>(cted().inst().card());
|
||||
let (typ_id, _) = cted().inst().card().ops::<A>();
|
||||
let mut data = enc_vec(&typ_id);
|
||||
self.encode(Pin::<&mut Vec<u8>>::new(&mut data)).await;
|
||||
obj_store.objects.read().await.insert(atom_id, Box::new(self));
|
||||
api::Atom { drop: Some(atom_id), data: api::AtomData(data), owner: sys_id() }
|
||||
api::LocalAtom { drop: Some(atom_id), data: api::AtomData(data) }
|
||||
})
|
||||
}
|
||||
fn _info() -> Self::_Info { OwnedAtomDynfo { msbuild: A::reg_reqs(), ms: OnceCell::new() } }
|
||||
type _Info = OwnedAtomDynfo<A>;
|
||||
fn _info() -> Self::_Info { OwnedAtomOps { msbuild: A::reg_methods(), ms: OnceCell::new() } }
|
||||
type _Info = OwnedAtomOps<A>;
|
||||
}
|
||||
|
||||
/// While an atom read guard is held, no atom can be removed.
|
||||
@@ -80,17 +79,15 @@ pub(crate) async fn take_atom(id: api::AtomId) -> Box<dyn DynOwnedAtom> {
|
||||
g.remove(&id).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0))
|
||||
}
|
||||
|
||||
pub struct OwnedAtomDynfo<T: OwnedAtom> {
|
||||
pub(crate) struct OwnedAtomOps<T: OwnedAtom> {
|
||||
msbuild: MethodSetBuilder<T>,
|
||||
ms: OnceCell<MethodSet<T>>,
|
||||
}
|
||||
impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
|
||||
fn tid(&self) -> TypeId { TypeId::of::<T>() }
|
||||
fn name(&self) -> &'static str { type_name::<T>() }
|
||||
impl<A: OwnedAtom> AtomOps for OwnedAtomOps<A> {
|
||||
fn tid(&self) -> TypeId { TypeId::of::<A>() }
|
||||
fn name(&self) -> &'static str { type_name::<A>() }
|
||||
fn decode<'a>(&'a self, AtomCtx(data, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
|
||||
Box::pin(async {
|
||||
Box::new(<T as AtomCard>::Data::decode_slice(&mut &data[..])) as Box<dyn Any>
|
||||
})
|
||||
Box::pin(async { Box::new(<A as Atomic>::Data::decode_slice(&mut &data[..])) as Box<dyn Any> })
|
||||
}
|
||||
fn call(&self, AtomCtx(_, id): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
|
||||
Box::pin(async move {
|
||||
@@ -123,7 +120,7 @@ impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
|
||||
&'a self,
|
||||
AtomCtx(_, id): AtomCtx<'a>,
|
||||
key: Sym,
|
||||
req: Box<dyn orchid_base::reqnot::ReqReader<'a> + 'a>,
|
||||
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
|
||||
) -> LocalBoxFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let a = AtomReadGuard::new(id.unwrap()).await;
|
||||
@@ -131,10 +128,7 @@ impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
|
||||
ms.dispatch(a.as_any_ref().downcast_ref().unwrap(), key, req).await
|
||||
})
|
||||
}
|
||||
fn command<'a>(
|
||||
&'a self,
|
||||
AtomCtx(_, id): AtomCtx<'a>,
|
||||
) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>> {
|
||||
fn command<'a>(&'a self, AtomCtx(_, id): AtomCtx<'a>) -> LocalBoxFuture<'a, CmdResult> {
|
||||
Box::pin(async move { take_atom(id.unwrap()).await.dyn_command().await })
|
||||
}
|
||||
fn drop(&self, AtomCtx(_, id): AtomCtx) -> LocalBoxFuture<'_, ()> {
|
||||
@@ -151,19 +145,34 @@ impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
|
||||
AtomReadGuard::new(id).await.dyn_serialize(write).await
|
||||
})
|
||||
}
|
||||
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> {
|
||||
fn deserialize<'a>(
|
||||
&'a self,
|
||||
data: &'a [u8],
|
||||
refs: &'a [Expr],
|
||||
) -> LocalBoxFuture<'a, api::LocalAtom> {
|
||||
Box::pin(async move {
|
||||
let refs = T::Refs::from_iter(refs.iter().cloned());
|
||||
let obj = T::deserialize(DeserCtxImpl(data), refs).await;
|
||||
let refs = A::Refs::from_iter(refs.iter().cloned());
|
||||
let obj = A::deserialize(DeserCtxImpl(data), refs).await;
|
||||
obj._factory().build().await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from the buffer populated by a previous call to [OwnedAtom::serialize]
|
||||
pub trait DeserializeCtx: Sized {
|
||||
/// Read a value from the head of the buffer
|
||||
fn read<T: Decode>(&mut self) -> impl Future<Output = T>;
|
||||
/// Check if the buffer is empty
|
||||
fn is_empty(&self) -> bool;
|
||||
/// # Panics
|
||||
///
|
||||
/// if the buffer isn't empty
|
||||
fn assert_empty(&self) { assert!(self.is_empty(), "Bytes found after decoding") }
|
||||
/// Decode the only value in the buffer
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the buffer has more data after the value was read
|
||||
fn decode<T: Decode>(&mut self) -> impl Future<Output = T> {
|
||||
async {
|
||||
let t = self.read().await;
|
||||
@@ -179,6 +188,8 @@ impl DeserializeCtx for DeserCtxImpl<'_> {
|
||||
fn is_empty(&self) -> bool { self.0.is_empty() }
|
||||
}
|
||||
|
||||
/// Various collections of expr's that distinguish how many references the type
|
||||
/// holds. See [OwnedAtom::Refs] for the list of permitted values
|
||||
pub trait RefSet {
|
||||
fn from_iter<I: Iterator<Item = Expr> + ExactSizeIterator>(refs: I) -> Self;
|
||||
fn to_vec(self) -> Vec<Expr>;
|
||||
@@ -211,10 +222,12 @@ impl<const N: usize> RefSet for [Expr; N] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Atoms that have a [Drop]
|
||||
/// Atoms that have a [Drop]. Internal mutability is allowed for optimization
|
||||
/// purposes, but new references to [Expr] should not be added to avoid
|
||||
/// reference loops
|
||||
pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
||||
/// If serializable, the collection that best stores subexpression references
|
||||
/// for this atom.
|
||||
/// for this type.
|
||||
///
|
||||
/// - `()` for no subexppressions,
|
||||
/// - `[Expr; N]` for a static number of subexpressions
|
||||
@@ -222,30 +235,38 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
||||
/// - `Never` if not serializable
|
||||
///
|
||||
/// If this isn't `Never`, you must override the default, panicking
|
||||
/// `serialize` and `deserialize` implementation
|
||||
/// [Self::serialize] and [Self::deserialize] implementation
|
||||
type Refs: RefSet;
|
||||
/// Obtain serializable representation
|
||||
fn val(&self) -> impl Future<Output = Cow<'_, Self::Data>>;
|
||||
/// Apply as a function while a different reference to the value exists.
|
||||
#[allow(unused_variables)]
|
||||
fn call_ref(&self, arg: Expr) -> impl Future<Output = GExpr> {
|
||||
fn call_ref(&self, arg: Expr) -> impl Future<Output: ToExpr> {
|
||||
async move { bot(err_not_callable(&self.dyn_print().await).await) }
|
||||
}
|
||||
fn call(self, arg: Expr) -> impl Future<Output = GExpr> {
|
||||
/// Apply as a function and consume
|
||||
fn call(self, arg: Expr) -> impl Future<Output: ToExpr> {
|
||||
async {
|
||||
let gcl = self.call_ref(arg).await;
|
||||
let gcl = self.call_ref(arg).await.to_gen().await;
|
||||
self.free().await;
|
||||
gcl
|
||||
}
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn command(self) -> impl Future<Output = OrcRes<Option<GExpr>>> {
|
||||
async move { Err(err_not_command(&self.dyn_print().await).await) }
|
||||
/// Called when this is the final value of the program
|
||||
fn command(self) -> impl Future<Output = CmdResult> {
|
||||
async move { Err(CmdError::Orc(err_not_command(&self.dyn_print().await).await)) }
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
/// Drop and perform any cleanup. Unlike Rust's [Drop::drop], this is
|
||||
/// guaranteed to be called
|
||||
fn free(self) -> impl Future<Output = ()> { async {} }
|
||||
/// Debug-print. This is the final fallback for Orchid's
|
||||
/// `std::string::to_str`.
|
||||
#[allow(unused_variables)]
|
||||
fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> {
|
||||
async { format!("OwnedAtom({})", type_name::<Self>()).into() }
|
||||
}
|
||||
/// Serialize this object. If the object is serializable you must override
|
||||
/// this function, otherwise set [Self::Refs] to [Never].
|
||||
#[allow(unused_variables)]
|
||||
fn serialize(
|
||||
&self,
|
||||
@@ -254,6 +275,8 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
||||
assert_serializable::<Self>();
|
||||
async { panic!("Either implement serialize or set Refs to Never for {}", type_name::<Self>()) }
|
||||
}
|
||||
/// Deserialize this object. If the object is serializable you must override
|
||||
/// this function, otherwise set [Self::Refs] to [Never]
|
||||
#[allow(unused_variables)]
|
||||
fn deserialize(dctx: impl DeserializeCtx, refs: Self::Refs) -> impl Future<Output = Self> {
|
||||
assert_serializable::<Self>();
|
||||
@@ -263,18 +286,18 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug-assert that the object opted in to serialization
|
||||
fn assert_serializable<T: OwnedAtom>() {
|
||||
static MSG: &str = "The extension scaffold is broken, Never Refs should prevent serialization";
|
||||
assert_ne!(TypeId::of::<T::Refs>(), TypeId::of::<Never>(), "{MSG}");
|
||||
debug_assert_ne!(TypeId::of::<T::Refs>(), TypeId::of::<Never>(), "{MSG}");
|
||||
}
|
||||
|
||||
pub trait DynOwnedAtom: DynClone + 'static {
|
||||
fn atom_tid(&self) -> TypeId;
|
||||
pub(crate) trait DynOwnedAtom: DynClone + 'static {
|
||||
fn as_any_ref(&self) -> &dyn Any;
|
||||
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()>;
|
||||
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr>;
|
||||
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr>;
|
||||
fn dyn_command(self: Box<Self>) -> LocalBoxFuture<'static, OrcRes<Option<GExpr>>>;
|
||||
fn dyn_command(self: Box<Self>) -> LocalBoxFuture<'static, CmdResult>;
|
||||
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
|
||||
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit>;
|
||||
fn dyn_serialize<'a>(
|
||||
@@ -283,20 +306,17 @@ pub trait DynOwnedAtom: DynClone + 'static {
|
||||
) -> LocalBoxFuture<'a, Option<Vec<Expr>>>;
|
||||
}
|
||||
impl<T: OwnedAtom> DynOwnedAtom for T {
|
||||
fn atom_tid(&self) -> TypeId { TypeId::of::<T>() }
|
||||
fn as_any_ref(&self) -> &dyn Any { self }
|
||||
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()> {
|
||||
async { self.val().await.as_ref().encode(buffer).await.unwrap() }.boxed_local()
|
||||
}
|
||||
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
|
||||
self.call_ref(arg).boxed_local()
|
||||
async { self.call_ref(arg).await.to_gen().await }.boxed_local()
|
||||
}
|
||||
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr> {
|
||||
self.call(arg).boxed_local()
|
||||
}
|
||||
fn dyn_command(self: Box<Self>) -> LocalBoxFuture<'static, OrcRes<Option<GExpr>>> {
|
||||
self.command().boxed_local()
|
||||
async { self.call(arg).await.to_gen().await }.boxed_local()
|
||||
}
|
||||
fn dyn_command(self: Box<Self>) -> LocalBoxFuture<'static, CmdResult> { Box::pin(self.command()) }
|
||||
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()> { self.free().boxed_local() }
|
||||
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit> {
|
||||
async move { self.print_atom(&FmtCtxImpl::default()).await }.boxed_local()
|
||||
@@ -330,14 +350,8 @@ pub(crate) fn get_obj_store() -> Rc<ObjStore> {
|
||||
OBJ_STORE.try_with(|store| store.clone()).expect("Owned atom store not initialized")
|
||||
}
|
||||
|
||||
pub async fn own<A: OwnedAtom>(typ: &TAtom<A>) -> A {
|
||||
let g = get_obj_store().objects.read().await;
|
||||
let atom_id = typ.untyped.atom.drop.expect("Owned atoms always have a drop ID");
|
||||
let dyn_atom =
|
||||
g.get(&atom_id).expect("Atom ID invalid; atom type probably not owned by this crate");
|
||||
dyn_atom.as_any_ref().downcast_ref().cloned().expect("The ID should imply a type as well")
|
||||
}
|
||||
|
||||
/// Debug-print the entire object store. Most useful if the interpreter refuses
|
||||
/// to shut down due to apparent refloops
|
||||
pub async fn debug_print_obj_store(show_atoms: bool) {
|
||||
let store = get_obj_store();
|
||||
let keys = store.objects.read().await.keys().cloned().collect_vec();
|
||||
|
||||
@@ -6,40 +6,38 @@ use async_once_cell::OnceCell;
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::{AsyncWrite, FutureExt};
|
||||
use orchid_api_traits::{Coding, enc_vec};
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::format::FmtUnit;
|
||||
use orchid_base::logging::log;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::{FmtUnit, Sym, log};
|
||||
|
||||
use crate::api;
|
||||
use crate::atom::{
|
||||
AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
|
||||
MethodSetBuilder, err_not_callable, err_not_command, get_info,
|
||||
AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
|
||||
MethodSetBuilder, err_not_callable, err_not_command,
|
||||
};
|
||||
use crate::expr::Expr;
|
||||
use crate::gen_expr::{GExpr, bot};
|
||||
use crate::system::{cted, sys_id};
|
||||
use crate::system::{DynSystemCardExt, cted};
|
||||
use crate::{CmdResult, api};
|
||||
|
||||
/// Value of [Atomic::Variant] for a type that implements [ThinAtom]
|
||||
pub struct ThinVariant;
|
||||
impl AtomicVariant for ThinVariant {}
|
||||
impl<A: ThinAtom + Atomic<Variant = ThinVariant>> AtomicFeaturesImpl<ThinVariant> for A {
|
||||
fn _factory(self) -> AtomFactory {
|
||||
AtomFactory::new(async move || {
|
||||
let (id, _) = get_info::<A>(cted().inst().card());
|
||||
let (id, _) = cted().inst().card().ops::<A>();
|
||||
let mut buf = enc_vec(&id);
|
||||
self.encode_vec(&mut buf);
|
||||
api::Atom { drop: None, data: api::AtomData(buf), owner: sys_id() }
|
||||
api::LocalAtom { drop: None, data: api::AtomData(buf) }
|
||||
})
|
||||
}
|
||||
fn _info() -> Self::_Info { ThinAtomDynfo { msbuild: Self::reg_reqs(), ms: OnceCell::new() } }
|
||||
type _Info = ThinAtomDynfo<Self>;
|
||||
fn _info() -> Self::_Info { ThinAtomOps { msbuild: Self::reg_methods(), ms: OnceCell::new() } }
|
||||
type _Info = ThinAtomOps<Self>;
|
||||
}
|
||||
|
||||
pub struct ThinAtomDynfo<T: ThinAtom> {
|
||||
pub(crate) struct ThinAtomOps<T: ThinAtom> {
|
||||
msbuild: MethodSetBuilder<T>,
|
||||
ms: OnceCell<MethodSet<T>>,
|
||||
}
|
||||
impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
|
||||
impl<T: ThinAtom> AtomOps for ThinAtomOps<T> {
|
||||
fn print<'a>(&self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> {
|
||||
Box::pin(async move { T::decode_slice(&mut &buf[..]).print().await })
|
||||
}
|
||||
@@ -58,17 +56,14 @@ impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
|
||||
&'a self,
|
||||
AtomCtx(buf, ..): AtomCtx<'a>,
|
||||
key: Sym,
|
||||
req: Box<dyn orchid_base::reqnot::ReqReader<'a> + 'a>,
|
||||
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
|
||||
) -> LocalBoxFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
|
||||
ms.dispatch(&T::decode_slice(&mut &buf[..]), key, req).await
|
||||
})
|
||||
}
|
||||
fn command<'a>(
|
||||
&'a self,
|
||||
AtomCtx(buf, _): AtomCtx<'a>,
|
||||
) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>> {
|
||||
fn command<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, CmdResult> {
|
||||
async move { T::decode_slice(&mut &buf[..]).command().await }.boxed_local()
|
||||
}
|
||||
fn serialize<'a, 'b: 'a>(
|
||||
@@ -81,7 +76,11 @@ impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
|
||||
Some(Vec::new())
|
||||
})
|
||||
}
|
||||
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> {
|
||||
fn deserialize<'a>(
|
||||
&'a self,
|
||||
data: &'a [u8],
|
||||
refs: &'a [Expr],
|
||||
) -> LocalBoxFuture<'a, api::LocalAtom> {
|
||||
assert!(refs.is_empty(), "Refs found when deserializing thin atom");
|
||||
Box::pin(async { T::decode_slice(&mut &data[..])._factory().build().await })
|
||||
}
|
||||
@@ -93,16 +92,15 @@ impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ThinAtom:
|
||||
AtomCard<Data = Self> + Atomic<Variant = ThinVariant> + Coding + Send + Sync + 'static
|
||||
{
|
||||
/// A simple value that is serializable and does not reference any other values
|
||||
pub trait ThinAtom: Atomic<Data = Self> + Atomic<Variant = ThinVariant> + Coding + 'static {
|
||||
#[allow(unused_variables)]
|
||||
fn call(&self, arg: Expr) -> impl Future<Output = GExpr> {
|
||||
async move { bot(err_not_callable(&self.print().await).await) }
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn command(&self) -> impl Future<Output = OrcRes<Option<GExpr>>> {
|
||||
async move { Err(err_not_command(&self.print().await).await) }
|
||||
fn command(&self) -> impl Future<Output = CmdResult> {
|
||||
async move { Err(err_not_command(&self.print().await).await.into()) }
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn print(&self) -> impl Future<Output = FmtUnit> {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
use orchid_base::binary::future_to_vt;
|
||||
use orchid_base::future_to_vt;
|
||||
|
||||
use crate::api;
|
||||
use crate::entrypoint::ExtensionBuilder;
|
||||
@@ -14,19 +15,23 @@ impl Drop for Spawner {
|
||||
fn drop(&mut self) { (self.0.drop)(self.0.data) }
|
||||
}
|
||||
impl Spawner {
|
||||
pub fn spawn(&self, fut: LocalBoxFuture<'static, ()>) {
|
||||
(self.0.spawn)(self.0.data, future_to_vt(fut))
|
||||
pub fn spawn(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) {
|
||||
(self.0.spawn)(self.0.data, delay.as_millis().try_into().unwrap(), future_to_vt(fut))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn orchid_extension_main_body(cx: ExtCx, builder: ExtensionBuilder) {
|
||||
let spawner = Spawner(cx.spawner);
|
||||
builder.build(ExtPort {
|
||||
input: Box::pin(cx.input),
|
||||
output: Box::pin(cx.output),
|
||||
log: Box::pin(cx.log),
|
||||
spawn: Rc::new(move |fut| spawner.spawn(fut)),
|
||||
});
|
||||
let spawner = Rc::new(Spawner(cx.spawner));
|
||||
let spawner2 = spawner.clone();
|
||||
spawner2.spawn(
|
||||
Duration::ZERO,
|
||||
Box::pin(builder.run(ExtPort {
|
||||
input: Box::pin(cx.input),
|
||||
output: Box::pin(cx.output),
|
||||
log: Box::pin(cx.log),
|
||||
spawn: Rc::new(move |delay, fut| spawner.spawn(delay, fut)),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate entrypoint for the dylib extension loader
|
||||
|
||||
@@ -3,16 +3,17 @@ use std::pin::Pin;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use never::Never;
|
||||
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
|
||||
use orchid_base::format::{Format, fmt};
|
||||
use orchid_base::interner::is;
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::{Format, OrcErrv, OrcRes, Pos, fmt, is, mk_errv};
|
||||
use trait_set::trait_set;
|
||||
|
||||
use crate::atom::{AtomicFeatures, ForeignAtom, TAtom};
|
||||
use crate::expr::{Expr, ExprKind};
|
||||
use crate::gen_expr::{GExpr, bot};
|
||||
|
||||
/// Attempt to cast a generic Orchid expression reference to a concrete value.
|
||||
/// Note that this cannot evaluate the expression, and if it is not already
|
||||
/// evaluated, it will simply fail. Use [crate::ExecHandle::exec] inside
|
||||
/// [crate::exec] to wait for an expression to be evaluated
|
||||
pub trait TryFromExpr: Sized {
|
||||
fn try_from_expr(expr: Expr) -> impl Future<Output = OrcRes<Self>>;
|
||||
}
|
||||
@@ -27,6 +28,8 @@ impl<T: TryFromExpr, U: TryFromExpr> TryFromExpr for (T, U) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Error raised when a composite expression was assumed to be an
|
||||
/// [crate::Atomic], or if the expression was not evaluated yet
|
||||
async fn err_not_atom(pos: Pos, value: &impl Format) -> OrcErrv {
|
||||
mk_errv(is("Expected an atom").await, format!("{} is not an atom", fmt(value).await), [pos])
|
||||
}
|
||||
@@ -46,21 +49,43 @@ impl TryFromExpr for ForeignAtom {
|
||||
impl<A: AtomicFeatures> TryFromExpr for TAtom<A> {
|
||||
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
||||
let f = ForeignAtom::try_from_expr(expr).await?;
|
||||
match f.clone().downcast::<A>().await {
|
||||
match f.clone().downcast::<A>() {
|
||||
Ok(a) => Ok(a),
|
||||
Err(e) => Err(e.mk_err().await),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Values that are convertible to an Orchid expression. This could mean that
|
||||
/// the value owns an [Expr] or it may involve more complex operations
|
||||
pub trait ToExpr {
|
||||
/// Inline the value in an expression returned from a function or included in
|
||||
/// the const tree returned by [crate::System::env]
|
||||
fn to_gen(self) -> impl Future<Output = GExpr>;
|
||||
/// Convert the value into a freestanding expression
|
||||
fn to_expr(self) -> impl Future<Output = Expr>
|
||||
where Self: Sized {
|
||||
async { self.to_gen().await.create().await }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToExprFuture<F>(pub F);
|
||||
|
||||
impl<F: Future<Output: ToExpr>> ToExpr for ToExprFuture<F> {
|
||||
async fn to_gen(self) -> GExpr { self.0.await.to_gen().await }
|
||||
async fn to_expr(self) -> Expr
|
||||
where Self: Sized {
|
||||
self.0.await.to_expr().await
|
||||
}
|
||||
}
|
||||
impl<F: Future> Future for ToExprFuture<F> {
|
||||
type Output = F::Output;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
|
||||
unsafe { self.map_unchecked_mut(|this| &mut this.0) }.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type-erased [ToExpr]
|
||||
pub trait ToExprDyn {
|
||||
fn to_gen_dyn<'a>(self: Box<Self>) -> Pin<Box<dyn Future<Output = GExpr> + 'a>>
|
||||
where Self: 'a;
|
||||
@@ -79,6 +104,8 @@ impl<T: ToExpr> ToExprDyn for T {
|
||||
}
|
||||
}
|
||||
trait_set! {
|
||||
/// type-erased [ToExpr] and [Clone]. Needed for a value to be
|
||||
/// included in [crate::System::env]
|
||||
pub trait ClonableToExprDyn = ToExprDyn + DynClone;
|
||||
}
|
||||
impl ToExpr for Box<dyn ToExprDyn> {
|
||||
|
||||
@@ -7,17 +7,16 @@ use futures::lock::Mutex;
|
||||
use futures::stream::{self, LocalBoxStream};
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use never::Never;
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::OrcRes;
|
||||
|
||||
use crate::atom::Atomic;
|
||||
use crate::atom_owned::{OwnedAtom, OwnedVariant};
|
||||
use crate::conv::{ToExpr, TryFromExpr};
|
||||
use crate::expr::Expr;
|
||||
use crate::gen_expr::{GExpr, arg, call, lambda, new_atom, seq};
|
||||
use crate::gen_expr::{GExpr, arg, call, lam, new_atom, seq};
|
||||
|
||||
enum Command {
|
||||
Execute(GExpr, Sender<Expr>),
|
||||
Register(GExpr, Sender<Expr>),
|
||||
Halt(GExpr),
|
||||
}
|
||||
|
||||
@@ -33,12 +32,9 @@ impl BuilderCoroutine {
|
||||
match cmd {
|
||||
None => panic!("Before the stream ends, we should have gotten a Halt"),
|
||||
Some(Command::Halt(expr)) => expr,
|
||||
Some(Command::Execute(expr, reply)) => call(
|
||||
lambda(0, [seq([arg(0)], call(new_atom(Replier { reply, builder: self }), [arg(0)]))]),
|
||||
[expr],
|
||||
),
|
||||
Some(Command::Register(expr, reply)) =>
|
||||
call(new_atom(Replier { reply, builder: self }), [expr]),
|
||||
Some(Command::Execute(expr, reply)) =>
|
||||
call(lam::<0>(seq(arg(0), call(new_atom(Replier { reply, builder: self }), arg(0)))), expr)
|
||||
.await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +51,7 @@ impl Atomic for Replier {
|
||||
impl OwnedAtom for Replier {
|
||||
type Refs = Never;
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||
async fn call(mut self, arg: Expr) -> GExpr {
|
||||
async fn call(mut self, arg: Expr) -> impl ToExpr {
|
||||
self.reply.send(arg).await.expect("What the heck");
|
||||
std::mem::drop(self.reply);
|
||||
self.builder.run().await
|
||||
@@ -81,9 +77,4 @@ impl ExecHandle<'_> {
|
||||
self.0.send(Command::Execute(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR);
|
||||
T::try_from_expr(reply_recv.next().await.expect(WEIRD_DROP_ERR)).await
|
||||
}
|
||||
pub async fn register(&mut self, val: impl ToExpr) -> Expr {
|
||||
let (reply_snd, mut reply_recv) = channel(1);
|
||||
self.0.send(Command::Register(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR);
|
||||
reply_recv.next().await.expect(WEIRD_DROP_ERR)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::num::NonZero;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::future::{LocalBoxFuture, join_all};
|
||||
use futures::{AsyncWriteExt, StreamExt, stream};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec};
|
||||
use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter};
|
||||
use orchid_base::error::try_with_reporter;
|
||||
use orchid_base::interner::{es, is, with_interner};
|
||||
use orchid_base::logging::{log, with_logger};
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::parse::{Comment, Snippet};
|
||||
use orchid_base::reqnot::{
|
||||
Client, ClientExt, CommCtx, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt, Witness, io_comm,
|
||||
use orchid_async_utils::{Handle, to_task};
|
||||
use orchid_base::{
|
||||
Client, ClientExt, CommCtx, Comment, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt,
|
||||
Snippet, Sym, TokenVariant, Witness, char_filter_match, char_filter_union, es, io_comm, is, log,
|
||||
mk_char_filter, try_with_reporter, ttv_from_api, with_interner, with_logger, with_stash,
|
||||
};
|
||||
use orchid_base::stash::with_stash;
|
||||
use orchid_base::tree::{TokenVariant, ttv_from_api};
|
||||
use substack::Substack;
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
use crate::atom::{AtomCtx, AtomTypeId, resolve_atom_type};
|
||||
use crate::atom_owned::{take_atom, with_obj_store};
|
||||
use crate::expr::{BorrowedExprStore, Expr, ExprHandle};
|
||||
@@ -35,10 +32,11 @@ use crate::lexer::{LexContext, ekey_cascade, ekey_not_applicable};
|
||||
use crate::logger::LoggerImpl;
|
||||
use crate::parser::{PTokTree, ParsCtx, get_const, linev_into_api, with_parsed_const_ctx};
|
||||
use crate::reflection::with_refl_roots;
|
||||
use crate::system::{SysCtx, atom_by_idx, cted, with_sys};
|
||||
use crate::system::{DynSystemCardExt, SysCtx, cted, with_sys};
|
||||
use crate::system_ctor::{CtedObj, DynSystemCtor, SystemCtor};
|
||||
use crate::tree::{TreeIntoApiCtxImpl, get_lazy, with_lazy_member_store};
|
||||
use crate::trivial_req::TrivialReqCycle;
|
||||
use crate::{api, encode_command_result};
|
||||
|
||||
task_local::task_local! {
|
||||
static CLIENT: Rc<dyn Client>;
|
||||
@@ -100,6 +98,33 @@ impl<F: AsyncFnOnce(LocalBoxFuture<'_, ()>) + 'static> ContextModifier for F {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait DynTaskHandle: 'static {
|
||||
fn abort(self: Box<Self>);
|
||||
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>>;
|
||||
}
|
||||
|
||||
task_local! {
|
||||
pub(crate) static SPAWN:
|
||||
Rc<dyn Fn(Duration, LocalBoxFuture<'static, Box<dyn Any>>) -> Box<dyn DynTaskHandle> + 'static>
|
||||
}
|
||||
|
||||
pub struct TaskHandle<T>(Box<dyn DynTaskHandle>, PhantomData<T>);
|
||||
impl<T: 'static> TaskHandle<T> {
|
||||
pub fn abort(self) { self.0.abort(); }
|
||||
pub async fn join(self) -> T { *self.0.join().await.downcast().unwrap() }
|
||||
}
|
||||
|
||||
pub fn spawn<F: Future<Output: 'static> + 'static>(delay: Duration, f: F) -> TaskHandle<F::Output> {
|
||||
SPAWN.with(|spawn| {
|
||||
TaskHandle(spawn(delay, Box::pin(async { Box::new(f.await) as Box<dyn Any> })), PhantomData)
|
||||
})
|
||||
}
|
||||
|
||||
impl DynTaskHandle for Handle<Box<dyn Any>> {
|
||||
fn abort(self: Box<Self>) { Self::abort(&self); }
|
||||
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>> { Box::pin(Self::join(*self)) }
|
||||
}
|
||||
|
||||
pub struct ExtensionBuilder {
|
||||
pub name: &'static str,
|
||||
pub systems: Vec<Box<dyn DynSystemCtor>>,
|
||||
@@ -118,275 +143,272 @@ impl ExtensionBuilder {
|
||||
self.add_context(fun);
|
||||
self
|
||||
}
|
||||
pub fn build(mut self, mut ctx: ExtPort) {
|
||||
pub async fn run(mut self, mut ctx: ExtPort) {
|
||||
self.add_context(with_funs_ctx);
|
||||
self.add_context(with_parsed_const_ctx);
|
||||
self.add_context(with_obj_store);
|
||||
self.add_context(with_lazy_member_store);
|
||||
self.add_context(with_refl_roots);
|
||||
(ctx.spawn)(Box::pin(async move {
|
||||
let host_header = api::HostHeader::decode(ctx.input.as_mut()).await.unwrap();
|
||||
let decls = (self.systems.iter().enumerate())
|
||||
.map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys))
|
||||
.map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap())))
|
||||
.collect_vec();
|
||||
api::ExtensionHeader { name: self.name.to_string(), systems: decls.clone() }
|
||||
.encode(ctx.output.as_mut())
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.output.as_mut().flush().await.unwrap();
|
||||
let logger1 = LoggerImpl::from_api(&host_header.logger);
|
||||
let logger2 = logger1.clone();
|
||||
let (client, comm_ctx, extension_srv) = io_comm(ctx.output, ctx.input);
|
||||
let extension_fut = extension_srv.listen(
|
||||
async |n: Box<dyn MsgReader<'_>>| {
|
||||
let notif = n.read().await.unwrap();
|
||||
match notif {
|
||||
api::HostExtNotif::Exit => exit().await,
|
||||
let spawn = ctx.spawn.clone();
|
||||
let host_header = api::HostHeader::decode(ctx.input.as_mut()).await.unwrap();
|
||||
let decls = (self.systems.iter().enumerate())
|
||||
.map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys))
|
||||
.map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap())))
|
||||
.collect_vec();
|
||||
api::ExtensionHeader { name: self.name.to_string(), systems: decls.clone() }
|
||||
.encode(ctx.output.as_mut())
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.output.as_mut().flush().await.unwrap();
|
||||
let logger1 = LoggerImpl::from_api(&host_header.logger);
|
||||
let logger2 = logger1.clone();
|
||||
let (client, comm_ctx, extension_srv) = io_comm(ctx.output, ctx.input);
|
||||
// this future will be ready once the extension cleanly exits
|
||||
let extension_fut = extension_srv.listen(
|
||||
async |n: Box<dyn MsgReader<'_>>| {
|
||||
let notif = n.read().await.unwrap();
|
||||
match notif {
|
||||
api::HostExtNotif::Exit => exit().await,
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
async |mut reader| {
|
||||
with_stash(async {
|
||||
let req = reader.read_req().await.unwrap();
|
||||
let handle = reader.finish().await;
|
||||
// Atom printing is never reported because it generates too much
|
||||
// noise
|
||||
if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) {
|
||||
writeln!(log("msg"), "{} extension received request {req:?}", self.name).await;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
async |mut reader| {
|
||||
with_stash(async {
|
||||
let req = reader.read_req().await.unwrap();
|
||||
let handle = reader.finish().await;
|
||||
// Atom printing is never reported because it generates too much
|
||||
// noise
|
||||
if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) {
|
||||
writeln!(log("msg"), "{} extension received request {req:?}", self.name).await;
|
||||
}
|
||||
match req {
|
||||
api::HostExtReq::SystemDrop(sys_drop) => {
|
||||
SYSTEM_TABLE.with(|l| l.borrow_mut().remove(&sys_drop.0));
|
||||
handle.reply(&sys_drop, &()).await
|
||||
},
|
||||
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
|
||||
with_sys_record(sys_id, async {
|
||||
take_atom(atom).await.dyn_free().await;
|
||||
handle.reply(&atom_drop, &()).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::Ping(ping @ api::Ping) => handle.reply(&ping, &()).await,
|
||||
api::HostExtReq::Sweep(api::Sweep) => todo!(),
|
||||
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
|
||||
let (ctor_idx, _) =
|
||||
(decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
|
||||
.expect("NewSystem call received for invalid system");
|
||||
let cted = self.systems[ctor_idx].new_system(&new_sys);
|
||||
let record = Rc::new(SystemRecord { cted: cted.clone() });
|
||||
SYSTEM_TABLE.with(|tbl| {
|
||||
let mut g = tbl.borrow_mut();
|
||||
g.insert(new_sys.id, record);
|
||||
});
|
||||
with_sys_record(new_sys.id, async {
|
||||
let lex_filter =
|
||||
cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| {
|
||||
char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned()))
|
||||
});
|
||||
let const_root = stream::iter(cted.inst().dyn_env().await)
|
||||
.then(async |mem| {
|
||||
let name = is(&mem.name).await;
|
||||
let mut tia_ctx = TreeIntoApiCtxImpl {
|
||||
basepath: &[],
|
||||
path: Substack::Bottom.push(name.clone()),
|
||||
};
|
||||
(name.to_api(), mem.kind.into_api(&mut tia_ctx).await)
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
let prelude =
|
||||
cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect();
|
||||
let line_types = join_all(
|
||||
(cted.inst().dyn_parsers().iter())
|
||||
.map(async |p| is(p.line_head()).await.to_api()),
|
||||
)
|
||||
match req {
|
||||
api::HostExtReq::SystemDrop(sys_drop) => {
|
||||
SYSTEM_TABLE.with(|l| l.borrow_mut().remove(&sys_drop.0));
|
||||
handle.reply(&sys_drop, &()).await
|
||||
},
|
||||
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
|
||||
with_sys_record(sys_id, async {
|
||||
take_atom(atom).await.dyn_free().await;
|
||||
handle.reply(&atom_drop, &()).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::Ping(ping @ api::Ping) => handle.reply(&ping, &()).await,
|
||||
api::HostExtReq::Sweep(api::Sweep) => todo!(),
|
||||
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
|
||||
let (ctor_idx, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
|
||||
.expect("NewSystem call received for invalid system");
|
||||
let cted = self.systems[ctor_idx].new_system(&new_sys);
|
||||
let record = Rc::new(SystemRecord { cted: cted.clone() });
|
||||
SYSTEM_TABLE.with(|tbl| {
|
||||
let mut g = tbl.borrow_mut();
|
||||
g.insert(new_sys.id, record);
|
||||
});
|
||||
with_sys_record(new_sys.id, async {
|
||||
let lex_filter =
|
||||
cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| {
|
||||
char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned()))
|
||||
});
|
||||
let const_root = stream::iter(cted.inst().dyn_env().await)
|
||||
.then(async |mem| {
|
||||
let name = is(&mem.name).await;
|
||||
let mut tia_ctx = TreeIntoApiCtxImpl {
|
||||
basepath: &[],
|
||||
path: Substack::Bottom.push(name.clone()),
|
||||
};
|
||||
(name.to_api(), mem.kind.into_api(&mut tia_ctx).await)
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
let response =
|
||||
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
|
||||
handle.reply(&new_sys, &response).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) =>
|
||||
with_sys_record(sys_id, async {
|
||||
let (path, tree) = get_lazy(tree_id).await;
|
||||
let mut tia_ctx =
|
||||
TreeIntoApiCtxImpl { path: Substack::Bottom, basepath: &path[..] };
|
||||
handle.reply(&get_tree, &tree.into_api(&mut tia_ctx).await).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
|
||||
let fwd_tok = Witness::of(&fwd);
|
||||
let api::SysFwded(sys_id, payload) = fwd;
|
||||
with_sys_record(sys_id, async {
|
||||
let mut reply = Vec::new();
|
||||
let req = TrivialReqCycle { req: &payload, rep: &mut reply };
|
||||
let _ = cted().inst().dyn_request(Box::new(req)).await;
|
||||
handle.reply(fwd_tok, &reply).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
|
||||
with_sys_record(sys, async {
|
||||
let text = es(text).await;
|
||||
let src = Sym::from_api(src).await;
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let tail = &text[pos as usize..];
|
||||
let trigger_char = tail.chars().next().unwrap();
|
||||
let ekey_na = ekey_not_applicable().await;
|
||||
let ekey_cascade = ekey_cascade().await;
|
||||
let lexers = cted().inst().dyn_lexers();
|
||||
for lx in
|
||||
lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char))
|
||||
{
|
||||
let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone());
|
||||
match try_with_reporter(lx.lex(tail, &ctx)).await {
|
||||
Err(e) if e.any(|e| *e == ekey_na) => continue,
|
||||
Err(e) => {
|
||||
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
|
||||
expr_store.dispose().await;
|
||||
return handle.reply(&lex, &eopt).await;
|
||||
},
|
||||
Ok((s, expr)) => {
|
||||
let expr = join_all(
|
||||
(expr.into_iter())
|
||||
.map(|tok| async { tok.into_api(&mut (), &mut ()).await }),
|
||||
)
|
||||
.await;
|
||||
let pos = (text.len() - s.len()) as u32;
|
||||
expr_store.dispose().await;
|
||||
return handle.reply(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await;
|
||||
let prelude =
|
||||
cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect();
|
||||
let line_types = join_all(
|
||||
(cted.inst().dyn_parsers().iter())
|
||||
.map(async |p| is(p.line_head()).await.to_api()),
|
||||
)
|
||||
.await;
|
||||
let response =
|
||||
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
|
||||
handle.reply(&new_sys, &response).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) =>
|
||||
with_sys_record(sys_id, async {
|
||||
let (path, tree) = get_lazy(tree_id).await;
|
||||
let mut tia_ctx =
|
||||
TreeIntoApiCtxImpl { path: Substack::Bottom, basepath: &path[..] };
|
||||
handle.reply(&get_tree, &tree.into_api(&mut tia_ctx).await).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
|
||||
let fwd_tok = Witness::of(&fwd);
|
||||
let api::SysFwded(sys_id, payload) = fwd;
|
||||
with_sys_record(sys_id, async {
|
||||
let mut reply = Vec::new();
|
||||
let req = TrivialReqCycle { req: &payload, rep: &mut reply };
|
||||
let _ = cted().inst().dyn_request(Box::new(req)).await;
|
||||
handle.reply(fwd_tok, &reply).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
|
||||
with_sys_record(sys, async {
|
||||
let text = es(text).await;
|
||||
let src = Sym::from_api(src).await;
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let tail = &text[pos as usize..];
|
||||
let trigger_char = tail.chars().next().unwrap();
|
||||
let ekey_na = ekey_not_applicable().await;
|
||||
let ekey_cascade = ekey_cascade().await;
|
||||
let lexers = cted().inst().dyn_lexers();
|
||||
for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char))
|
||||
{
|
||||
let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone());
|
||||
match try_with_reporter(lx.lex(tail, &ctx)).await {
|
||||
Err(e) if e.any(|e| *e == ekey_na) => continue,
|
||||
Err(e) => {
|
||||
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
|
||||
expr_store.dispose().await;
|
||||
return handle.reply(&lex, &eopt).await;
|
||||
},
|
||||
Ok((s, expr)) => {
|
||||
let expr = join_all(
|
||||
(expr.into_iter())
|
||||
.map(|tok| async { tok.into_api(&mut (), &mut ()).await }),
|
||||
)
|
||||
.await;
|
||||
let pos = (text.len() - s.len()) as u32;
|
||||
expr_store.dispose().await;
|
||||
return handle.reply(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
writeln!(log("warn"), "Got notified about n/a character '{trigger_char}'").await;
|
||||
expr_store.dispose().await;
|
||||
handle.reply(&lex, &None).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::ParseLine(pline) => {
|
||||
let req = Witness::of(&pline);
|
||||
let api::ParseLine { module, src, exported, comments, sys, line, idx } = pline;
|
||||
with_sys_record(sys, async {
|
||||
let parsers = cted().inst().dyn_parsers();
|
||||
let src = Sym::from_api(src).await;
|
||||
let comments =
|
||||
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone()))).await;
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let line: Vec<PTokTree> = ttv_from_api(line, &mut &expr_store, &mut (), &src).await;
|
||||
let snip = Snippet::new(line.first().expect("Empty line"), &line);
|
||||
let parser = parsers[idx as usize];
|
||||
let module = Sym::from_api(module).await;
|
||||
let pctx = ParsCtx::new(module);
|
||||
let o_line =
|
||||
match try_with_reporter(parser.parse(pctx, exported, comments, snip)).await {
|
||||
Err(e) => Err(e.to_api()),
|
||||
Ok(t) => Ok(linev_into_api(t).await),
|
||||
};
|
||||
mem::drop(line);
|
||||
expr_store.dispose().await;
|
||||
handle.reply(req, &o_line).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
|
||||
with_sys_record(sys, async {
|
||||
let cnst = get_const(id).await;
|
||||
handle.reply(fpc, &cnst.serialize().await).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::AtomReq(atom_req) => {
|
||||
let atom = atom_req.get_atom();
|
||||
with_sys_record(atom.owner, async {
|
||||
let (nfo, id, buf) = resolve_atom_type(atom);
|
||||
let actx = AtomCtx(buf, atom.drop);
|
||||
match &atom_req {
|
||||
api::AtomReq::SerializeAtom(ser) => {
|
||||
let mut buf = enc_vec(&id);
|
||||
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
|
||||
None => handle.reply(ser, &None).await,
|
||||
Some(refs) => {
|
||||
let refs =
|
||||
join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await))
|
||||
.await;
|
||||
handle.reply(ser, &Some((buf, refs))).await
|
||||
},
|
||||
}
|
||||
}
|
||||
writeln!(log("warn"), "Got notified about n/a character '{trigger_char}'").await;
|
||||
expr_store.dispose().await;
|
||||
handle.reply(&lex, &None).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::ParseLine(pline) => {
|
||||
let api::ParseLine { module, src, exported, comments, sys, line, idx } = &pline;
|
||||
with_sys_record(*sys, async {
|
||||
let parsers = cted().inst().dyn_parsers();
|
||||
let src = Sym::from_api(*src).await;
|
||||
let comments =
|
||||
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone()))).await;
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let line: Vec<PTokTree> =
|
||||
ttv_from_api(line, &mut &expr_store, &mut (), &src).await;
|
||||
let snip = Snippet::new(line.first().expect("Empty line"), &line);
|
||||
let parser = parsers[*idx as usize];
|
||||
let module = Sym::from_api(*module).await;
|
||||
let pctx = ParsCtx::new(module);
|
||||
let o_line =
|
||||
match try_with_reporter(parser.parse(pctx, *exported, comments, snip)).await {
|
||||
Err(e) => Err(e.to_api()),
|
||||
Ok(t) => Ok(linev_into_api(t).await),
|
||||
};
|
||||
mem::drop(line);
|
||||
expr_store.dispose().await;
|
||||
handle.reply(&pline, &o_line).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
|
||||
with_sys_record(sys, async {
|
||||
let cnst = get_const(id).await;
|
||||
handle.reply(fpc, &cnst.serialize().await).await
|
||||
})
|
||||
.await,
|
||||
api::HostExtReq::AtomReq(atom_req) => {
|
||||
let atom = atom_req.get_atom();
|
||||
with_sys_record(atom.owner, async {
|
||||
let (nfo, id, buf) = resolve_atom_type(atom);
|
||||
let actx = AtomCtx(buf, atom.drop);
|
||||
match &atom_req {
|
||||
api::AtomReq::SerializeAtom(ser) => {
|
||||
let mut buf = enc_vec(&id);
|
||||
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
|
||||
None => handle.reply(ser, &None).await,
|
||||
Some(refs) => {
|
||||
let refs =
|
||||
join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await))
|
||||
.await;
|
||||
handle.reply(ser, &Some((buf, refs))).await
|
||||
},
|
||||
}
|
||||
},
|
||||
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
|
||||
handle.reply(print, &nfo.print(actx).await.to_api()).await,
|
||||
api::AtomReq::Fwded(fwded) => {
|
||||
let api::Fwded(_, key, payload) = &fwded;
|
||||
let mut reply = Vec::new();
|
||||
let key = Sym::from_api(*key).await;
|
||||
let req = TrivialReqCycle { req: payload, rep: &mut reply };
|
||||
let some = nfo.handle_req(actx, key, Box::new(req)).await;
|
||||
handle.reply(fwded, &some.then_some(reply)).await
|
||||
},
|
||||
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
||||
let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await;
|
||||
let api_expr = ret.serialize().await;
|
||||
mem::drop(expr_handle);
|
||||
expr_store.dispose().await;
|
||||
handle.reply(call, &api_expr).await
|
||||
},
|
||||
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
||||
let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await;
|
||||
let api_expr = ret.serialize().await;
|
||||
mem::drop(expr_handle);
|
||||
expr_store.dispose().await;
|
||||
handle.reply(call, &api_expr).await
|
||||
},
|
||||
api::AtomReq::Command(cmd @ api::Command(_)) => match nfo.command(actx).await {
|
||||
Err(e) => handle.reply(cmd, &Err(e.to_api())).await,
|
||||
Ok(opt) => match opt {
|
||||
None => handle.reply(cmd, &Ok(api::NextStep::Halt)).await,
|
||||
Some(cont) => {
|
||||
let cont = cont.serialize().await;
|
||||
handle.reply(cmd, &Ok(api::NextStep::Continue(cont))).await
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::DeserAtom(deser) => {
|
||||
let api::DeserAtom(sys, buf, refs) = &deser;
|
||||
let read = &mut &buf[..];
|
||||
with_sys_record(*sys, async {
|
||||
// SAFETY: deserialization implicitly grants ownership to previously owned exprs
|
||||
let refs = (refs.iter())
|
||||
.map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk)))
|
||||
.collect_vec();
|
||||
let id = AtomTypeId::decode_slice(read);
|
||||
let nfo = atom_by_idx(cted().inst().card(), id)
|
||||
.expect("Deserializing atom with invalid ID");
|
||||
handle.reply(&deser, &nfo.deserialize(read, &refs).await).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
},
|
||||
);
|
||||
// add essential services to the very tail, then fold all context into the run
|
||||
// future
|
||||
SYSTEM_TABLE
|
||||
.scope(
|
||||
RefCell::default(),
|
||||
with_interner(
|
||||
new_interner(),
|
||||
with_logger(
|
||||
logger2,
|
||||
with_comm(
|
||||
Rc::new(client),
|
||||
comm_ctx,
|
||||
},
|
||||
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
|
||||
handle.reply(print, &nfo.print(actx).await.to_api()).await,
|
||||
api::AtomReq::Fwded(fwded) => {
|
||||
let api::Fwded(_, key, payload) = &fwded;
|
||||
let mut reply = Vec::new();
|
||||
let key = Sym::from_api(*key).await;
|
||||
let req = TrivialReqCycle { req: payload, rep: &mut reply };
|
||||
let some = nfo.handle_req(actx, key, Box::new(req)).await;
|
||||
handle.reply(fwded, &some.then_some(reply)).await
|
||||
},
|
||||
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
||||
let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await;
|
||||
let api_expr = ret.serialize().await;
|
||||
mem::drop(expr_handle);
|
||||
expr_store.dispose().await;
|
||||
handle.reply(call, &api_expr).await
|
||||
},
|
||||
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
|
||||
let expr_store = BorrowedExprStore::new();
|
||||
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
||||
let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await;
|
||||
let api_expr = ret.serialize().await;
|
||||
mem::drop(expr_handle);
|
||||
expr_store.dispose().await;
|
||||
handle.reply(call, &api_expr).await
|
||||
},
|
||||
api::AtomReq::Command(cmd @ api::Command(_)) =>
|
||||
handle.reply(cmd, &encode_command_result(nfo.command(actx).await).await).await,
|
||||
}
|
||||
})
|
||||
.await
|
||||
},
|
||||
api::HostExtReq::DeserAtom(deser) => {
|
||||
let api::DeserAtom(sys, buf, refs) = &deser;
|
||||
let read = &mut &buf[..];
|
||||
with_sys_record(*sys, async {
|
||||
// SAFETY: deserialization implicitly grants ownership to previously owned exprs
|
||||
let refs = (refs.iter())
|
||||
.map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk)))
|
||||
.collect_vec();
|
||||
let id = AtomTypeId::decode_slice(read);
|
||||
let nfo = (cted().inst().card().ops_by_atid(id))
|
||||
.expect("Deserializing atom with invalid ID");
|
||||
handle.reply(&deser, &nfo.deserialize(read, &refs).await).await
|
||||
})
|
||||
.await
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
},
|
||||
);
|
||||
// add essential services to the very tail, then fold all context into the run
|
||||
// future
|
||||
SYSTEM_TABLE
|
||||
.scope(
|
||||
RefCell::default(),
|
||||
with_interner(
|
||||
new_interner(),
|
||||
with_logger(
|
||||
logger2,
|
||||
with_comm(
|
||||
Rc::new(client),
|
||||
comm_ctx,
|
||||
SPAWN.scope(
|
||||
Rc::new(move |delay, fut| {
|
||||
let (poll, handle) = to_task(fut);
|
||||
spawn(delay, Box::pin(poll));
|
||||
Box::new(handle)
|
||||
}),
|
||||
(self.context.into_iter()).fold(
|
||||
Box::pin(async { extension_fut.await.unwrap() }) as LocalBoxFuture<()>,
|
||||
|fut, cx| cx.apply(fut),
|
||||
@@ -394,8 +416,8 @@ impl ExtensionBuilder {
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}) as Pin<Box<_>>);
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::{fmt, iter};
|
||||
|
||||
use dyn_clone::{clone_box, DynClone};
|
||||
use itertools::Itertools;
|
||||
use orchid_base::boxed_iter::{box_once, BoxedIter};
|
||||
use orchid_base::clone;
|
||||
use orchid_base::error::{ErrPos, OrcError};
|
||||
use orchid_base::interner::{deintern, intern};
|
||||
use orchid_base::location::{GetSrc, Pos};
|
||||
use orchid_base::reqnot::{ReqNot, Requester};
|
||||
|
||||
use crate::api;
|
||||
|
||||
/// Errors addressed to the developer which are to be resolved with
|
||||
/// code changes
|
||||
pub trait ProjectError: Sized + Send + Sync + 'static {
|
||||
/// A general description of this type of error
|
||||
const DESCRIPTION: &'static str;
|
||||
/// A formatted message that includes specific parameters
|
||||
#[must_use]
|
||||
fn message(&self) -> String { self.description().to_string() }
|
||||
/// Code positions relevant to this error. If you don't implement this, you
|
||||
/// must implement [ProjectError::one_position]
|
||||
#[must_use]
|
||||
fn positions(&self) -> impl IntoIterator<Item = ErrPos> + '_ {
|
||||
box_once(ErrPos { position: self.one_position(), message: None })
|
||||
}
|
||||
/// Short way to provide a single origin. If you don't implement this, you
|
||||
/// must implement [ProjectError::positions]
|
||||
#[must_use]
|
||||
fn one_position(&self) -> Pos {
|
||||
unimplemented!("Error type did not implement either positions or one_position")
|
||||
}
|
||||
/// Convert the error into an `Arc<dyn DynProjectError>` to be able to
|
||||
/// handle various errors together
|
||||
#[must_use]
|
||||
fn pack(self) -> ProjectErrorObj { Arc::new(self) }
|
||||
}
|
||||
|
||||
/// Object-safe version of [ProjectError]. Implement that instead of this.
|
||||
pub trait DynProjectError: Send + Sync + 'static {
|
||||
/// Access type information about this error
|
||||
#[must_use]
|
||||
fn as_any_ref(&self) -> &dyn Any;
|
||||
/// Pack the error into a trait object, or leave it as-is if it's already a
|
||||
/// trait object
|
||||
#[must_use]
|
||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj;
|
||||
/// A general description of this type of error
|
||||
#[must_use]
|
||||
fn description(&self) -> Cow<'_, str>;
|
||||
/// A formatted message that includes specific parameters
|
||||
#[must_use]
|
||||
fn message(&self) -> String { self.description().to_string() }
|
||||
/// Code positions relevant to this error.
|
||||
#[must_use]
|
||||
fn positions(&self) -> BoxedIter<'_, ErrPos>;
|
||||
}
|
||||
|
||||
impl<T> DynProjectError for T
|
||||
where T: ProjectError
|
||||
{
|
||||
fn as_any_ref(&self) -> &dyn Any { self }
|
||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
|
||||
fn description(&self) -> Cow<'_, str> { Cow::Borrowed(T::DESCRIPTION) }
|
||||
fn message(&self) -> String { ProjectError::message(self) }
|
||||
fn positions(&self) -> BoxedIter<ErrPos> { Box::new(ProjectError::positions(self).into_iter()) }
|
||||
}
|
||||
|
||||
pub fn pretty_print(err: &dyn DynProjectError, get_src: &mut impl GetSrc) -> String {
|
||||
let description = err.description();
|
||||
let message = err.message();
|
||||
let positions = err.positions().collect::<Vec<_>>();
|
||||
let head = format!("Project error: {description}\n{message}");
|
||||
if positions.is_empty() {
|
||||
head + "No origins specified"
|
||||
} else {
|
||||
iter::once(head)
|
||||
.chain(positions.iter().map(|ErrPos { position: origin, message }| match message {
|
||||
None => format!("@{}", origin.pretty_print(get_src)),
|
||||
Some(msg) => format!("@{}: {msg}", origin.pretty_print(get_src)),
|
||||
}))
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl DynProjectError for ProjectErrorObj {
|
||||
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
|
||||
fn description(&self) -> Cow<'_, str> { (**self).description() }
|
||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { (*self).clone() }
|
||||
fn message(&self) -> String { (**self).message() }
|
||||
fn positions(&self) -> BoxedIter<'_, ErrPos> { (**self).positions() }
|
||||
}
|
||||
|
||||
/// Type-erased [ProjectError] implementor through the [DynProjectError]
|
||||
/// object-trait
|
||||
pub type ProjectErrorObj = Arc<dyn DynProjectError>;
|
||||
/// Alias for a result with an error of [ProjectErrorObj]. This is the type of
|
||||
/// result most commonly returned by pre-runtime operations.
|
||||
pub type ProjectResult<T> = Result<T, ProjectErrorObj>;
|
||||
|
||||
/// A trait for error types that are only missing an origin. Do not depend on
|
||||
/// this trait, refer to [DynErrorSansOrigin] instead.
|
||||
pub trait ErrorSansOrigin: Clone + Sized + Send + Sync + 'static {
|
||||
/// General description of the error condition
|
||||
const DESCRIPTION: &'static str;
|
||||
/// Specific description of the error including code fragments or concrete
|
||||
/// data if possible
|
||||
fn message(&self) -> String { Self::DESCRIPTION.to_string() }
|
||||
/// Convert the error to a type-erased structure for handling on shared
|
||||
/// channels
|
||||
fn pack(self) -> ErrorSansOriginObj { Box::new(self) }
|
||||
/// A shortcut to streamline switching code between [ErrorSansOriginObj] and
|
||||
/// concrete types
|
||||
fn bundle(self, origin: &Pos) -> ProjectErrorObj { self.pack().bundle(origin) }
|
||||
}
|
||||
|
||||
/// Object-safe equivalent to [ErrorSansOrigin]. Implement that one instead of
|
||||
/// this. Typically found as [ErrorSansOriginObj]
|
||||
pub trait DynErrorSansOrigin: Any + Send + Sync + DynClone {
|
||||
/// Allow to downcast the base object to distinguish between various errors.
|
||||
/// The main intended purpose is to trigger a fallback when [CodeNotFound] is
|
||||
/// encountered, but the possibilities are not limited to that.
|
||||
fn as_any_ref(&self) -> &dyn Any;
|
||||
/// Regularize the type
|
||||
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj;
|
||||
/// Generic description of the error condition
|
||||
fn description(&self) -> Cow<'_, str>;
|
||||
/// Specific description of this particular error
|
||||
fn message(&self) -> String;
|
||||
/// Add an origin
|
||||
fn bundle(self: Box<Self>, origin: &Pos) -> ProjectErrorObj;
|
||||
}
|
||||
|
||||
/// Type-erased [ErrorSansOrigin] implementor through the object-trait
|
||||
/// [DynErrorSansOrigin]. This can be turned into a [ProjectErrorObj] with
|
||||
/// [ErrorSansOriginObj::bundle].
|
||||
pub type ErrorSansOriginObj = Box<dyn DynErrorSansOrigin>;
|
||||
/// A generic project result without origin
|
||||
pub type ResultSansOrigin<T> = Result<T, ErrorSansOriginObj>;
|
||||
|
||||
impl<T: ErrorSansOrigin + 'static> DynErrorSansOrigin for T {
|
||||
fn description(&self) -> Cow<'_, str> { Cow::Borrowed(Self::DESCRIPTION) }
|
||||
fn message(&self) -> String { (*self).message() }
|
||||
fn as_any_ref(&self) -> &dyn Any { self }
|
||||
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj { (*self).pack() }
|
||||
fn bundle(self: Box<Self>, origin: &Pos) -> ProjectErrorObj {
|
||||
Arc::new(OriginBundle(origin.clone(), *self))
|
||||
}
|
||||
}
|
||||
impl Clone for ErrorSansOriginObj {
|
||||
fn clone(&self) -> Self { clone_box(&**self) }
|
||||
}
|
||||
impl DynErrorSansOrigin for ErrorSansOriginObj {
|
||||
fn description(&self) -> Cow<'_, str> { (**self).description() }
|
||||
fn message(&self) -> String { (**self).message() }
|
||||
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
|
||||
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj { *self }
|
||||
fn bundle(self: Box<Self>, origin: &Pos) -> ProjectErrorObj { (*self).bundle(origin) }
|
||||
}
|
||||
impl fmt::Display for ErrorSansOriginObj {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "{}\nOrigin missing from error", self.message())
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for ErrorSansOriginObj {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") }
|
||||
}
|
||||
|
||||
struct OriginBundle<T: ErrorSansOrigin>(Pos, T);
|
||||
impl<T: ErrorSansOrigin> DynProjectError for OriginBundle<T> {
|
||||
fn as_any_ref(&self) -> &dyn Any { self.1.as_any_ref() }
|
||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
|
||||
fn description(&self) -> Cow<'_, str> { self.1.description() }
|
||||
fn message(&self) -> String { self.1.message() }
|
||||
fn positions(&self) -> BoxedIter<ErrPos> {
|
||||
box_once(ErrPos { position: self.0.clone(), message: None })
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection for tracking fatal errors without halting. Participating
|
||||
/// functions return [ProjectResult] even if they only ever construct [Ok]. When
|
||||
/// they call other participating functions, instead of directly forwarding
|
||||
/// errors with `?` they should prefer constructing a fallback value with
|
||||
/// [Reporter::fallback]. If any error is added to a [Reporter] in a function,
|
||||
/// the return value is valid but its meaning need not be related in any way to
|
||||
/// the inputs.
|
||||
///
|
||||
/// Returning [Err] from a function that accepts `&mut Reporter` indicates not
|
||||
/// that there was a fatal error but that it wasn't possible to construct a
|
||||
/// fallback, so if it can, the caller should construct one.
|
||||
pub struct Reporter(RefCell<Vec<ProjectErrorObj>>);
|
||||
impl Reporter {
|
||||
/// Create a new error reporter
|
||||
pub fn new() -> Self { Self(RefCell::new(Vec::new())) }
|
||||
/// Returns true if any errors were regorded. If this ever returns true, it
|
||||
/// will always return true in the future.
|
||||
pub fn failing(&self) -> bool { !self.0.borrow().is_empty() }
|
||||
/// Report a fatal error
|
||||
pub fn report(&self, error: ProjectErrorObj) {
|
||||
match error.as_any_ref().downcast_ref::<MultiError>() {
|
||||
None => self.0.borrow_mut().push(error),
|
||||
Some(me) =>
|
||||
for err in me.0.iter() {
|
||||
self.report(err.clone())
|
||||
},
|
||||
}
|
||||
}
|
||||
/// Catch a fatal error, report it, and substitute the value
|
||||
pub fn fallback<T>(&self, res: ProjectResult<T>, cb: impl FnOnce(ProjectErrorObj) -> T) -> T {
|
||||
res.inspect_err(|e| self.report(e.clone())).unwrap_or_else(cb)
|
||||
}
|
||||
/// Take the errors out of the reporter
|
||||
#[must_use]
|
||||
pub fn into_errors(self) -> Option<Vec<ProjectErrorObj>> {
|
||||
let v = self.0.into_inner();
|
||||
if v.is_empty() { None } else { Some(v) }
|
||||
}
|
||||
/// Raise an error if the reporter contains any errors
|
||||
pub fn bind(self) -> ProjectResult<()> {
|
||||
match self.into_errors() {
|
||||
None => Ok(()),
|
||||
Some(v) if v.len() == 1 => Err(v.into_iter().next().unwrap()),
|
||||
Some(v) => Err(MultiError(v).pack()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Reporter {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
fn unpack_into(err: impl DynProjectError, res: &mut Vec<ProjectErrorObj>) {
|
||||
match err.as_any_ref().downcast_ref::<MultiError>() {
|
||||
Some(multi) => multi.0.iter().for_each(|e| unpack_into(e.clone(), res)),
|
||||
None => res.push(Arc::new(err).into_packed()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unpack_err(err: ProjectErrorObj) -> Vec<ProjectErrorObj> {
|
||||
let mut out = Vec::new();
|
||||
unpack_into(err, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
pub fn pack_err<E: DynProjectError>(iter: impl IntoIterator<Item = E>) -> ProjectErrorObj {
|
||||
let mut errors = Vec::new();
|
||||
iter.into_iter().for_each(|e| unpack_into(e, &mut errors));
|
||||
if errors.len() == 1 { errors.into_iter().next().unwrap() } else { MultiError(errors).pack() }
|
||||
}
|
||||
|
||||
struct MultiError(Vec<ProjectErrorObj>);
|
||||
impl ProjectError for MultiError {
|
||||
const DESCRIPTION: &'static str = "Multiple errors occurred";
|
||||
fn message(&self) -> String { format!("{} errors occurred", self.0.len()) }
|
||||
fn positions(&self) -> impl IntoIterator<Item = ErrPos> + '_ {
|
||||
self.0.iter().flat_map(|e| {
|
||||
e.positions().map(|pos| {
|
||||
let emsg = e.message();
|
||||
let msg = match pos.message {
|
||||
None => emsg,
|
||||
Some(s) if s.is_empty() => emsg,
|
||||
Some(pmsg) => format!("{emsg}: {pmsg}"),
|
||||
};
|
||||
ErrPos { position: pos.position, message: Some(Arc::new(msg)) }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn err_to_api(err: ProjectErrorObj) -> api::OrcErr {
|
||||
api::OrcErr {
|
||||
description: intern(&*err.description()).marker(),
|
||||
message: Arc::new(err.message()),
|
||||
locations: err.positions().map(|e| e.to_api()).collect_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayedError {
|
||||
pub id: Option<api::ErrId>,
|
||||
pub reqnot: ReqNot<api::ExtMsgSet>,
|
||||
pub details: OnceLock<OrcError>,
|
||||
}
|
||||
impl RelayedError {
|
||||
fn details(&self) -> &OrcError {
|
||||
let Self { id, reqnot, details: data } = self;
|
||||
data.get_or_init(clone!(reqnot; move || {
|
||||
let id = id.expect("Either data or ID must be initialized");
|
||||
let projerr = reqnot.request(api::GetErrorDetails(id));
|
||||
OrcError {
|
||||
description: deintern(projerr.description),
|
||||
message: projerr.message,
|
||||
positions: projerr.locations.iter().map(ErrPos::from_api).collect_vec(),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl DynProjectError for RelayedError {
|
||||
fn description(&self) -> Cow<'_, str> { Cow::Borrowed(self.details().description.as_str()) }
|
||||
fn message(&self) -> String { self.details().message.to_string() }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
|
||||
fn positions(&self) -> BoxedIter<'_, ErrPos> {
|
||||
Box::new(self.details().positions.iter().cloned())
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
use std::thread::panicking;
|
||||
|
||||
use async_once_cell::OnceCell;
|
||||
use derive_destructure::destructure;
|
||||
use hashbrown::HashSet;
|
||||
use orchid_base::error::OrcErrv;
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format};
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::stash::stash;
|
||||
use orchid_base::{FmtCtx, FmtUnit, Format, OrcErrv, Pos, stash};
|
||||
|
||||
use crate::api;
|
||||
use crate::atom::ForeignAtom;
|
||||
@@ -123,7 +120,7 @@ impl Expr {
|
||||
let kind = match details.kind {
|
||||
api::InspectedKind::Atom(a) =>
|
||||
ExprKind::Atom(ForeignAtom::new(self.handle.clone(), a, pos.clone())),
|
||||
api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b).await),
|
||||
api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(b).await),
|
||||
api::InspectedKind::Opaque => ExprKind::Opaque,
|
||||
};
|
||||
ExprData { pos, kind }
|
||||
@@ -156,6 +153,13 @@ impl Format for Expr {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Eq for Expr {}
|
||||
impl PartialEq for Expr {
|
||||
fn eq(&self, other: &Self) -> bool { self.handle == other.handle }
|
||||
}
|
||||
impl Hash for Expr {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.handle.hash(state); }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExprData {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::{AsyncRead, AsyncWrite};
|
||||
@@ -8,5 +9,5 @@ pub struct ExtPort {
|
||||
pub input: Pin<Box<dyn AsyncRead>>,
|
||||
pub output: Pin<Box<dyn AsyncWrite>>,
|
||||
pub log: Pin<Box<dyn AsyncWrite>>,
|
||||
pub spawn: Rc<dyn Fn(LocalBoxFuture<'static, ()>)>,
|
||||
pub spawn: Rc<dyn Fn(Duration, LocalBoxFuture<'static, ()>)>,
|
||||
}
|
||||
|
||||
@@ -11,11 +11,7 @@ use futures::{AsyncWrite, FutureExt};
|
||||
use itertools::Itertools;
|
||||
use never::Never;
|
||||
use orchid_api_traits::Encode;
|
||||
use orchid_base::clone;
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::format::{FmtCtx, FmtUnit};
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::{FmtCtx, FmtUnit, OrcRes, Pos, Sym, clone};
|
||||
use task_local::task_local;
|
||||
use trait_set::trait_set;
|
||||
|
||||
@@ -129,7 +125,7 @@ impl Atomic for Fun {
|
||||
impl OwnedAtom for Fun {
|
||||
type Refs = Vec<Expr>;
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||
async fn call_ref(&self, arg: Expr) -> GExpr {
|
||||
async fn call_ref(&self, arg: Expr) -> impl ToExpr {
|
||||
let new_args = self.args.iter().cloned().chain([arg]).collect_vec();
|
||||
if new_args.len() == self.record.argtyps.len() {
|
||||
(self.record.fun)(new_args).await.to_gen().await
|
||||
@@ -137,7 +133,6 @@ impl OwnedAtom for Fun {
|
||||
new_atom(Self { args: new_args, record: self.record.clone(), path: self.path.clone() })
|
||||
}
|
||||
}
|
||||
async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await }
|
||||
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
|
||||
self.path.to_api().encode(write).await.unwrap();
|
||||
self.args.clone()
|
||||
@@ -175,7 +170,7 @@ impl Atomic for Lambda {
|
||||
impl OwnedAtom for Lambda {
|
||||
type Refs = Never;
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||
async fn call_ref(&self, arg: Expr) -> GExpr {
|
||||
async fn call_ref(&self, arg: Expr) -> impl ToExpr {
|
||||
let new_args = self.args.iter().cloned().chain([arg]).collect_vec();
|
||||
if new_args.len() == self.record.argtyps.len() {
|
||||
(self.record.fun)(new_args).await.to_gen().await
|
||||
@@ -183,14 +178,13 @@ impl OwnedAtom for Lambda {
|
||||
new_atom(Self { args: new_args, record: self.record.clone() })
|
||||
}
|
||||
}
|
||||
async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await }
|
||||
}
|
||||
|
||||
mod expr_func_derives {
|
||||
use std::any::TypeId;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::OrcRes;
|
||||
|
||||
use super::{ARGV, ExprFunc};
|
||||
use crate::conv::{ToExpr, TryFromExpr};
|
||||
@@ -224,7 +218,7 @@ mod expr_func_derives {
|
||||
expr_func_derive!(A, B, C);
|
||||
expr_func_derive!(A, B, C, D);
|
||||
expr_func_derive!(A, B, C, D, E);
|
||||
// expr_func_derive!(A, B, C, D, E, F);
|
||||
expr_func_derive!(A, B, C, D, E, F);
|
||||
// expr_func_derive!(A, B, C, D, E, F, G);
|
||||
// expr_func_derive!(A, B, C, D, E, F, G, H);
|
||||
// expr_func_derive!(A, B, C, D, E, F, G, H, I);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
use std::mem;
|
||||
use std::pin::{Pin, pin};
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::FutureExt;
|
||||
use orchid_base::error::{OrcErr, OrcErrv};
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::{match_mapping, tl_cache};
|
||||
use futures::{FutureExt, Stream, StreamExt, stream};
|
||||
use orchid_base::{
|
||||
FmtCtx, FmtUnit, Format, OrcErr, OrcErrv, Pos, Sym, Variants, match_mapping, tl_cache,
|
||||
};
|
||||
|
||||
use crate::api;
|
||||
use crate::atom::{AtomFactory, ToAtom};
|
||||
use crate::atom::{AtomFactory, AtomicFeatures};
|
||||
use crate::conv::{ToExpr, ToExprFuture};
|
||||
use crate::entrypoint::request;
|
||||
use crate::expr::Expr;
|
||||
use crate::system::sys_id;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GExpr {
|
||||
@@ -39,7 +40,7 @@ impl GExpr {
|
||||
}
|
||||
pub fn at(self, pos: Pos) -> Self { GExpr { pos, kind: self.kind } }
|
||||
pub async fn create(self) -> Expr {
|
||||
Expr::deserialize(request(api::Create(self.serialize().await)).await).await
|
||||
Expr::deserialize(request(api::Create(sys_id(), self.serialize().await)).await).await
|
||||
}
|
||||
}
|
||||
impl Format for GExpr {
|
||||
@@ -55,6 +56,7 @@ pub enum GExprKind {
|
||||
Arg(u64),
|
||||
Seq(Box<GExpr>, Box<GExpr>),
|
||||
Const(Sym),
|
||||
#[allow(private_interfaces)]
|
||||
NewAtom(AtomFactory),
|
||||
Slot(Expr),
|
||||
Bottom(OrcErrv),
|
||||
@@ -105,27 +107,105 @@ impl Format for GExprKind {
|
||||
|
||||
fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } }
|
||||
|
||||
pub fn sym_ref(path: Sym) -> GExpr { inherit(GExprKind::Const(path)) }
|
||||
/// Creates an expression from a new atom that we own.
|
||||
pub fn new_atom<A: ToAtom>(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.to_atom_factory())) }
|
||||
|
||||
pub fn seq(deps: impl IntoIterator<Item = GExpr>, val: GExpr) -> GExpr {
|
||||
fn recur(mut ops: impl Iterator<Item = GExpr>) -> Option<GExpr> {
|
||||
let op = ops.next()?;
|
||||
Some(match recur(ops) {
|
||||
None => op,
|
||||
Some(rec) => inherit(GExprKind::Seq(Box::new(op), Box::new(rec))),
|
||||
})
|
||||
impl ToExpr for Sym {
|
||||
async fn to_expr(self) -> Expr
|
||||
where Self: Sized {
|
||||
self.to_gen().await.create().await
|
||||
}
|
||||
recur(deps.into_iter().chain([val])).expect("Empty list provided to seq!")
|
||||
async fn to_gen(self) -> GExpr { inherit(GExprKind::Const(self)) }
|
||||
}
|
||||
/// Creates an expression from a new atom that we own.
|
||||
pub fn new_atom<A: AtomicFeatures>(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.factory())) }
|
||||
|
||||
pub fn seq(
|
||||
deps: impl IntoGExprStream,
|
||||
val: impl ToExpr,
|
||||
) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||
ToExprFuture(async {
|
||||
async fn recur(mut ops: Pin<&mut impl Stream<Item = GExpr>>) -> Option<GExpr> {
|
||||
let op = ops.next().await?.to_gen().await;
|
||||
Some(match recur(ops).boxed_local().await {
|
||||
None => op,
|
||||
Some(rec) => inherit(GExprKind::Seq(Box::new(op), Box::new(rec))),
|
||||
})
|
||||
}
|
||||
recur(pin!(deps.into_gexpr_stream().chain(stream::iter([val.to_gen().await]))))
|
||||
.await
|
||||
.expect("Empty list provided to seq!")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arg(n: u64) -> GExpr { inherit(GExprKind::Arg(n)) }
|
||||
|
||||
pub fn lambda(n: u64, [b]: [GExpr; 1]) -> GExpr { inherit(GExprKind::Lambda(n, Box::new(b))) }
|
||||
pub fn lam<const N: u64>(b: impl ToExpr) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||
dyn_lambda(N, b)
|
||||
}
|
||||
|
||||
pub fn call(f: GExpr, argv: impl IntoIterator<Item = GExpr>) -> GExpr {
|
||||
(argv.into_iter()).fold(f, |f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x))))
|
||||
pub fn dyn_lambda(n: u64, b: impl ToExpr) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||
ToExprFuture(async move { inherit(GExprKind::Lambda(n, Box::new(b.to_gen().await))) })
|
||||
}
|
||||
|
||||
pub trait IntoGExprStream {
|
||||
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr>;
|
||||
}
|
||||
impl<T: ToExpr> IntoGExprStream for T {
|
||||
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr> { (self,).into_gexpr_stream() }
|
||||
}
|
||||
impl IntoGExprStream for Vec<GExpr> {
|
||||
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr> { stream::iter(self) }
|
||||
}
|
||||
|
||||
mod tuple_impls {
|
||||
use futures::{Stream, StreamExt, stream};
|
||||
|
||||
use super::IntoGExprStream;
|
||||
use crate::conv::ToExpr;
|
||||
use crate::gen_expr::GExpr;
|
||||
|
||||
macro_rules! tuple_impl {
|
||||
($($T:ident)*) => {
|
||||
pastey::paste!{
|
||||
impl<$($T: ToExpr),*> IntoGExprStream for ($($T,)*) {
|
||||
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr> {
|
||||
let ($([< $T:snake >],)*) = self;
|
||||
stream::once(async { stream::iter([$([< $T:snake >].to_gen().await,)*]) }).flatten()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tuple_impl!();
|
||||
tuple_impl!(A);
|
||||
tuple_impl!(A B);
|
||||
tuple_impl!(A B C);
|
||||
tuple_impl!(A B C D);
|
||||
tuple_impl!(A B C D E);
|
||||
tuple_impl!(A B C D E F);
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
f: impl ToExpr,
|
||||
argv: impl IntoGExprStream,
|
||||
) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||
ToExprFuture(async {
|
||||
(argv.into_gexpr_stream())
|
||||
.fold(f.to_gen().await, async |f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x))))
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call_v(
|
||||
f: impl ToExpr,
|
||||
argv: impl IntoIterator<Item: ToExpr>,
|
||||
) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||
ToExprFuture(async {
|
||||
stream::iter(argv)
|
||||
.fold(f.to_gen().await, async |f, x| {
|
||||
inherit(GExprKind::Call(Box::new(f), Box::new(x.to_gen().await)))
|
||||
})
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bot(ev: impl IntoIterator<Item = OrcErr>) -> GExpr {
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::rc::Rc;
|
||||
|
||||
use futures::future::{LocalBoxFuture, join_all, ready};
|
||||
use itertools::Itertools;
|
||||
use orchid_base::interner::local_interner::{Int, StrBranch, StrvBranch};
|
||||
use orchid_base::interner::{IStr, IStrv, InternerSrv};
|
||||
use orchid_base::local_interner::{Int, StrBranch, StrvBranch};
|
||||
use orchid_base::{IStr, IStrv, InternerSrv};
|
||||
|
||||
use crate::api;
|
||||
use crate::entrypoint::{MUTE_REPLY, request};
|
||||
|
||||
@@ -5,10 +5,7 @@ use std::ops::RangeInclusive;
|
||||
|
||||
use futures::FutureExt;
|
||||
use futures::future::LocalBoxFuture;
|
||||
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
|
||||
use orchid_base::interner::{IStr, is};
|
||||
use orchid_base::location::{Pos, SrcRange};
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::{IStr, OrcErrv, OrcRes, Pos, SrcRange, Sym, is, mk_errv};
|
||||
|
||||
use crate::api;
|
||||
use crate::entrypoint::request;
|
||||
@@ -50,7 +47,7 @@ impl<'a> LexContext<'a> {
|
||||
}
|
||||
pub fn src(&self) -> &Sym { &self.src }
|
||||
/// This function returns [PTokTree] because it can never return
|
||||
/// [orchid_base::tree::Token::NewExpr]. You can use
|
||||
/// [orchid_base::Token::NewExpr]. You can use
|
||||
/// [crate::parser::p_tree2gen] to convert this to [crate::tree::GenTokTree]
|
||||
/// for embedding in the return value.
|
||||
pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, PTokTree)> {
|
||||
@@ -58,7 +55,7 @@ impl<'a> LexContext<'a> {
|
||||
let Some(lx) = request(api::SubLex { pos: start, id: self.id }).await else {
|
||||
return Err(err_cascade().await);
|
||||
};
|
||||
let tree = PTokTree::from_api(&lx.tree, &mut { self.exprs }, &mut (), &self.src).await;
|
||||
let tree = PTokTree::from_api(lx.tree, &mut { self.exprs }, &mut (), &self.src).await;
|
||||
Ok((&self.text[lx.pos as usize..], tree))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#![allow(refining_impl_trait, reason = "Has various false-positives around lints")]
|
||||
use orchid_api as api;
|
||||
|
||||
pub mod atom;
|
||||
pub mod atom_owned;
|
||||
pub mod atom_thin;
|
||||
mod atom;
|
||||
pub use atom::*;
|
||||
mod atom_owned;
|
||||
pub use atom_owned::*;
|
||||
mod atom_thin;
|
||||
pub use atom_thin::*;
|
||||
pub mod binary;
|
||||
pub mod conv;
|
||||
pub mod coroutine_exec;
|
||||
|
||||
@@ -5,8 +5,7 @@ use std::rc::Rc;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
use hashbrown::HashMap;
|
||||
use orchid_base::interner::is;
|
||||
use orchid_base::logging::{LogWriter, Logger};
|
||||
use orchid_base::{LogWriter, Logger, is};
|
||||
|
||||
use crate::api;
|
||||
use crate::entrypoint::notify;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
use std::pin::pin;
|
||||
|
||||
use async_once_cell::OnceCell;
|
||||
use futures::lock::Mutex;
|
||||
use orchid_base::msg::{recv_msg, send_msg};
|
||||
|
||||
pub async fn send_parent_msg(msg: &[u8]) -> io::Result<()> {
|
||||
let stdout_lk = STDOUT.get_or_init(async { Mutex::new(io::stdout()) }).await;
|
||||
let mut stdout_g = stdout_lk.lock().await;
|
||||
send_msg(pin!(&mut *stdout_g), msg).await
|
||||
}
|
||||
pub async fn recv_parent_msg() -> io::Result<Vec<u8>> { recv_msg(pin!(io::stdin())).await }
|
||||
@@ -1,18 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::num::NonZero;
|
||||
|
||||
use async_fn_stream::stream;
|
||||
use futures::future::{LocalBoxFuture, join_all};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use never::Never;
|
||||
use orchid_base::error::{OrcErrv, OrcRes};
|
||||
use orchid_base::id_store::IdStore;
|
||||
use orchid_base::interner::IStr;
|
||||
use orchid_base::location::SrcRange;
|
||||
use orchid_base::match_mapping;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::parse::{Comment, Snippet};
|
||||
use orchid_base::tree::{TokTree, Token, ttv_into_api};
|
||||
use orchid_base::{
|
||||
Comment, IStr, OrcErrv, OrcRes, Snippet, SrcRange, Sym, TokTree, Token, match_mapping,
|
||||
ttv_into_api,
|
||||
};
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
@@ -92,11 +91,13 @@ impl<'a> ParsCtx<'a> {
|
||||
type BoxConstCallback = Box<dyn FnOnce(ConstCtx) -> LocalBoxFuture<'static, GExpr>>;
|
||||
|
||||
task_local! {
|
||||
static PARSED_CONST_CTX: IdStore<BoxConstCallback>
|
||||
static CONST_TBL: RefCell<HashMap<NonZero<u64>, BoxConstCallback>>;
|
||||
static NEXT_CONST_ID: RefCell<NonZero<u64>>;
|
||||
}
|
||||
|
||||
pub(crate) fn with_parsed_const_ctx<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
|
||||
Box::pin(PARSED_CONST_CTX.scope(IdStore::default(), fut))
|
||||
let id_cell = RefCell::new(NonZero::new(1).unwrap());
|
||||
Box::pin(CONST_TBL.scope(RefCell::default(), NEXT_CONST_ID.scope(id_cell, fut)))
|
||||
}
|
||||
|
||||
pub struct ParsedLine {
|
||||
@@ -139,9 +140,11 @@ impl ParsedLine {
|
||||
name: mem.name.to_api(),
|
||||
exported: mem.exported,
|
||||
kind: match mem.kind {
|
||||
ParsedMemKind::Const(cb) => api::ParsedMemberKind::Constant(api::ParsedConstId(
|
||||
PARSED_CONST_CTX.with(|consts| consts.add(cb).id()),
|
||||
)),
|
||||
ParsedMemKind::Const(cb) => {
|
||||
let id = NEXT_CONST_ID.with(|c| c.replace_with(|id| id.checked_add(1).unwrap()));
|
||||
CONST_TBL.with(|consts| consts.borrow_mut().insert(id, cb));
|
||||
api::ParsedMemberKind::Constant(api::ParsedConstId(id))
|
||||
},
|
||||
ParsedMemKind::Mod { lines, use_prelude } => api::ParsedMemberKind::Module {
|
||||
lines: linev_into_api(lines).boxed_local().await,
|
||||
use_prelude,
|
||||
@@ -192,7 +195,7 @@ impl ConstCtx {
|
||||
let resolve_names = api::ResolveNames { constid: self.constid, sys: sys_id(), names };
|
||||
for name_opt in request(resolve_names).await {
|
||||
cx.emit(match name_opt {
|
||||
Err(e) => Err(OrcErrv::from_api(&e).await),
|
||||
Err(e) => Err(OrcErrv::from_api(e).await),
|
||||
Ok(name) => Ok(Sym::from_api(name).await),
|
||||
})
|
||||
.await
|
||||
@@ -205,7 +208,7 @@ impl ConstCtx {
|
||||
}
|
||||
|
||||
pub(crate) async fn get_const(id: api::ParsedConstId) -> GExpr {
|
||||
let cb = PARSED_CONST_CTX
|
||||
.with(|ent| ent.get(id.0).expect("Bad ID or double read of parsed const").remove());
|
||||
let cb = CONST_TBL
|
||||
.with(|ent| ent.borrow_mut().remove(&id.0).expect("Bad ID or double read of parsed const"));
|
||||
cb(ConstCtx { constid: id }).await
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ use futures::future::LocalBoxFuture;
|
||||
use futures::lock::Mutex;
|
||||
use hashbrown::HashMap;
|
||||
use memo_map::MemoMap;
|
||||
use orchid_base::interner::{IStr, es, iv};
|
||||
use orchid_base::name::{NameLike, VPath};
|
||||
use orchid_base::{IStr, NameLike, VPath, es, iv};
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::num::NonZero;
|
||||
|
||||
use orchid_api_derive::{Coding, Hierarchy};
|
||||
use orchid_api_traits::Request;
|
||||
|
||||
@@ -5,13 +7,23 @@ use crate::atom::AtomMethod;
|
||||
|
||||
/// Represents [std::io::ErrorKind] values that are produced while operating on
|
||||
/// already-opened files
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)]
|
||||
pub enum IoErrorKind {
|
||||
BrokenPipe,
|
||||
UnexpectedEof,
|
||||
ConnectionAborted,
|
||||
Other,
|
||||
}
|
||||
impl IoErrorKind {
|
||||
pub fn message(self) -> &'static str {
|
||||
match self {
|
||||
IoErrorKind::Other => "Failed to read from stream",
|
||||
IoErrorKind::BrokenPipe => "Broken pipe",
|
||||
IoErrorKind::UnexpectedEof => "Unexpected EOF",
|
||||
IoErrorKind::ConnectionAborted => "Connection aborted",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents [std::io::Error] values that are produced while operating on
|
||||
/// already-opened files
|
||||
@@ -21,10 +33,17 @@ pub struct IoError {
|
||||
pub kind: IoErrorKind,
|
||||
}
|
||||
|
||||
/// Read at most the specified number of bytes, but at least one byte, from a
|
||||
/// stream. If the returned vector is empty, the stream has reached its end.
|
||||
#[derive(Clone, Debug, Coding)]
|
||||
pub enum ReadLimit {
|
||||
End,
|
||||
Delimiter(u8),
|
||||
Length(NonZero<u64>),
|
||||
}
|
||||
|
||||
/// Read all available data from a stream. If the returned vector is empty, the
|
||||
/// stream has reached its end.
|
||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||
pub struct ReadReq(pub u64);
|
||||
pub struct ReadReq(pub ReadLimit);
|
||||
impl Request for ReadReq {
|
||||
type Response = Result<Vec<u8>, IoError>;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::any::{Any, TypeId, type_name};
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::num::NonZero;
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures::FutureExt;
|
||||
use futures::future::LocalBoxFuture;
|
||||
use orchid_api_traits::{Coding, Decode, Encode, Request};
|
||||
use orchid_base::boxed_iter::BoxedIter;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::reqnot::{Receipt, ReqHandle, ReqReader, ReqReaderExt};
|
||||
use orchid_base::{BoxedIter, Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym};
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TAtom, get_info};
|
||||
use crate::atom::{AtomOps, AtomTypeId, Atomic, AtomicFeatures};
|
||||
use crate::coroutine_exec::Replier;
|
||||
use crate::entrypoint::request;
|
||||
use crate::func_atom::{Fun, Lambda};
|
||||
@@ -22,96 +19,95 @@ use crate::parser::ParserObj;
|
||||
use crate::system_ctor::{CtedObj, SystemCtor};
|
||||
use crate::tree::GenMember;
|
||||
|
||||
/// System as consumed by foreign code
|
||||
pub trait SystemCard: Debug + Default + Send + Sync + 'static {
|
||||
/// Description of a system. This is a distinct object because [SystemCtor]
|
||||
/// isn't [Default]
|
||||
pub trait SystemCard: Debug + Default + 'static {
|
||||
type Ctor: SystemCtor;
|
||||
type Req: Coding;
|
||||
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomDynfo>>>;
|
||||
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomOps>>>;
|
||||
}
|
||||
|
||||
pub trait DynSystemCard: Send + Sync + Any + 'static {
|
||||
/// Type-erased [SystemCard]
|
||||
pub trait DynSystemCard: Any + 'static {
|
||||
fn name(&self) -> &'static str;
|
||||
/// Atoms explicitly defined by the system card. Do not rely on this for
|
||||
/// querying atoms as it doesn't include the general atom types
|
||||
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomDynfo>>>;
|
||||
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomOps>>>;
|
||||
}
|
||||
|
||||
impl<T: DynSystemCard + ?Sized> DynSystemCardExt for T {}
|
||||
pub(crate) trait DynSystemCardExt: DynSystemCard {
|
||||
fn ops_by_tid(&self, tid: TypeId) -> Option<(AtomTypeId, Box<dyn AtomOps>)> {
|
||||
(self.atoms().enumerate().map(|(i, o)| (NonZero::new(i as u32 + 1).unwrap(), o)))
|
||||
.chain(general_atoms().enumerate().map(|(i, o)| (NonZero::new(!(i as u32)).unwrap(), o)))
|
||||
.filter_map(|(i, o)| o.map(|a| (AtomTypeId(i), a)))
|
||||
.find(|ent| ent.1.tid() == tid)
|
||||
}
|
||||
fn ops_by_atid(&self, tid: AtomTypeId) -> Option<Box<dyn AtomOps>> {
|
||||
if (u32::from(tid.0) >> (u32::BITS - 1)) & 1 == 1 {
|
||||
general_atoms().nth(!u32::from(tid.0) as usize).unwrap()
|
||||
} else {
|
||||
self.atoms().nth(u32::from(tid.0) as usize - 1).unwrap()
|
||||
}
|
||||
}
|
||||
fn ops<A: Atomic>(&self) -> (AtomTypeId, Box<dyn AtomOps>) {
|
||||
self
|
||||
.ops_by_tid(TypeId::of::<A>())
|
||||
.unwrap_or_else(|| panic!("{} is not an atom in {}", type_name::<A>(), self.name()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Atoms supported by this package which may appear in all extensions.
|
||||
/// The indices of these are bitwise negated, such that the MSB of an atom index
|
||||
/// marks whether it belongs to this package (0) or the importer (1)
|
||||
fn general_atoms() -> impl Iterator<Item = Option<Box<dyn AtomDynfo>>> {
|
||||
[Some(Fun::dynfo()), Some(Lambda::dynfo()), Some(Replier::dynfo())].into_iter()
|
||||
}
|
||||
|
||||
pub fn atom_info_for(
|
||||
sys: &(impl DynSystemCard + ?Sized),
|
||||
tid: TypeId,
|
||||
) -> Option<(AtomTypeId, Box<dyn AtomDynfo>)> {
|
||||
(sys.atoms().enumerate().map(|(i, o)| (NonZero::new(i as u32 + 1).unwrap(), o)))
|
||||
.chain(general_atoms().enumerate().map(|(i, o)| (NonZero::new(!(i as u32)).unwrap(), o)))
|
||||
.filter_map(|(i, o)| o.map(|a| (AtomTypeId(i), a)))
|
||||
.find(|ent| ent.1.tid() == tid)
|
||||
}
|
||||
|
||||
pub fn atom_by_idx(
|
||||
sys: &(impl DynSystemCard + ?Sized),
|
||||
tid: AtomTypeId,
|
||||
) -> Option<Box<dyn AtomDynfo>> {
|
||||
if (u32::from(tid.0) >> (u32::BITS - 1)) & 1 == 1 {
|
||||
general_atoms().nth(!u32::from(tid.0) as usize).unwrap()
|
||||
} else {
|
||||
sys.atoms().nth(u32::from(tid.0) as usize - 1).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolv_atom(
|
||||
sys: &(impl DynSystemCard + ?Sized),
|
||||
atom: &api::Atom,
|
||||
) -> Box<dyn AtomDynfo> {
|
||||
let tid = AtomTypeId::decode(Pin::new(&mut &atom.data.0[..])).await.unwrap();
|
||||
atom_by_idx(sys, tid).expect("Value of nonexistent type found")
|
||||
fn general_atoms() -> impl Iterator<Item = Option<Box<dyn AtomOps>>> {
|
||||
[Some(Fun::ops()), Some(Lambda::ops()), Some(Replier::ops())].into_iter()
|
||||
}
|
||||
|
||||
impl<T: SystemCard> DynSystemCard for T {
|
||||
fn name(&self) -> &'static str { T::Ctor::NAME }
|
||||
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomDynfo>>> {
|
||||
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomOps>>> {
|
||||
Box::new(Self::atoms().into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
/// System as defined by author
|
||||
pub trait System: Send + Sync + SystemCard + 'static {
|
||||
fn prelude() -> impl Future<Output = Vec<Sym>>;
|
||||
fn env() -> impl Future<Output = Vec<GenMember>>;
|
||||
fn lexers() -> Vec<LexerObj>;
|
||||
fn parsers() -> Vec<ParserObj>;
|
||||
pub trait System: SystemCard + 'static {
|
||||
fn prelude(&self) -> impl Future<Output = Vec<Sym>>;
|
||||
fn env(&self) -> impl Future<Output = Vec<GenMember>>;
|
||||
fn lexers(&self) -> Vec<LexerObj>;
|
||||
fn parsers(&self) -> Vec<ParserObj>;
|
||||
fn request<'a>(
|
||||
&self,
|
||||
hand: Box<dyn ReqHandle<'a> + 'a>,
|
||||
req: Self::Req,
|
||||
) -> impl Future<Output = Receipt<'a>>;
|
||||
}
|
||||
|
||||
pub trait DynSystem: Send + Sync + DynSystemCard + 'static {
|
||||
pub trait DynSystem: DynSystemCard + 'static {
|
||||
fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec<Sym>>;
|
||||
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>>;
|
||||
fn dyn_lexers(&self) -> Vec<LexerObj>;
|
||||
fn dyn_parsers(&self) -> Vec<ParserObj>;
|
||||
fn dyn_request<'a>(&self, hand: Box<dyn ReqReader<'a> + 'a>) -> LocalBoxFuture<'a, Receipt<'a>>;
|
||||
fn dyn_request<'a, 'b: 'a>(
|
||||
&'a self,
|
||||
hand: Box<dyn ReqReader<'b> + 'b>,
|
||||
) -> LocalBoxFuture<'a, Receipt<'b>>;
|
||||
fn card(&self) -> &dyn DynSystemCard;
|
||||
}
|
||||
|
||||
impl<T: System> DynSystem for T {
|
||||
fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec<Sym>> { Box::pin(Self::prelude()) }
|
||||
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>> { Self::env().boxed_local() }
|
||||
fn dyn_lexers(&self) -> Vec<LexerObj> { Self::lexers() }
|
||||
fn dyn_parsers(&self) -> Vec<ParserObj> { Self::parsers() }
|
||||
fn dyn_request<'a>(
|
||||
&self,
|
||||
mut hand: Box<dyn ReqReader<'a> + 'a>,
|
||||
) -> LocalBoxFuture<'a, Receipt<'a>> {
|
||||
fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec<Sym>> { Box::pin(self.prelude()) }
|
||||
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>> { self.env().boxed_local() }
|
||||
fn dyn_lexers(&self) -> Vec<LexerObj> { self.lexers() }
|
||||
fn dyn_parsers(&self) -> Vec<ParserObj> { self.parsers() }
|
||||
fn dyn_request<'a, 'b: 'a>(
|
||||
&'a self,
|
||||
mut hand: Box<dyn ReqReader<'b> + 'b>,
|
||||
) -> LocalBoxFuture<'a, Receipt<'b>> {
|
||||
Box::pin(async move {
|
||||
let value = hand.read_req::<<Self as SystemCard>::Req>().await.unwrap();
|
||||
Self::request(hand.finish().await, value).await
|
||||
self.request(hand.finish().await, value).await
|
||||
})
|
||||
}
|
||||
fn card(&self) -> &dyn DynSystemCard { self }
|
||||
@@ -130,30 +126,6 @@ pub(crate) async fn with_sys<F: Future>(sys: SysCtx, fut: F) -> F::Output {
|
||||
|
||||
pub fn sys_id() -> api::SysId { SYS_CTX.with(|cx| cx.0) }
|
||||
pub fn cted() -> CtedObj { SYS_CTX.with(|cx| cx.1.clone()) }
|
||||
pub async fn downcast_atom<A>(foreign: ForeignAtom) -> Result<TAtom<A>, ForeignAtom>
|
||||
where A: AtomicFeatures {
|
||||
let mut data = &foreign.atom.data.0[..];
|
||||
let value = AtomTypeId::decode_slice(&mut data);
|
||||
let cted = cted();
|
||||
let own_inst = cted.inst();
|
||||
let owner = if sys_id() == foreign.atom.owner {
|
||||
own_inst.card()
|
||||
} else {
|
||||
cted.deps().find(|s| s.id() == foreign.atom.owner).ok_or_else(|| foreign.clone())?.get_card()
|
||||
};
|
||||
if owner.atoms().flatten().all(|dynfo| dynfo.tid() != TypeId::of::<A>()) {
|
||||
return Err(foreign);
|
||||
}
|
||||
let (typ_id, dynfo) = get_info::<A>(owner);
|
||||
if value != typ_id {
|
||||
return Err(foreign);
|
||||
}
|
||||
let val = dynfo.decode(AtomCtx(data, foreign.atom.drop)).await;
|
||||
let Ok(value) = val.downcast::<A::Data>() else {
|
||||
panic!("decode of {} returned wrong type.", dynfo.name());
|
||||
};
|
||||
Ok(TAtom { value: *value, untyped: foreign })
|
||||
}
|
||||
|
||||
/// Make a global request to a system that supports this request type. The
|
||||
/// target system must either be the system in which this function is called, or
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use orchid_base::boxed_iter::{BoxedIter, box_empty, box_once};
|
||||
use orchid_base::{BoxedIter, box_empty, box_once};
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use crate::api;
|
||||
@@ -17,7 +17,7 @@ pub struct Cted<Ctor: SystemCtor + ?Sized> {
|
||||
impl<C: SystemCtor + ?Sized> Clone for Cted<C> {
|
||||
fn clone(&self) -> Self { Self { deps: self.deps.clone(), inst: self.inst.clone() } }
|
||||
}
|
||||
pub trait DynCted: Debug + Send + Sync + 'static {
|
||||
pub trait DynCted: Debug + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn deps<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>;
|
||||
fn inst(&self) -> Arc<dyn DynSystem>;
|
||||
@@ -29,7 +29,7 @@ impl<C: SystemCtor + ?Sized> DynCted for Cted<C> {
|
||||
}
|
||||
pub type CtedObj = Arc<dyn DynCted>;
|
||||
|
||||
pub trait DepSat: Debug + Clone + Send + Sync + 'static {
|
||||
pub trait DepSat: Debug + Clone + 'static {
|
||||
fn iter<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl DepDef for () {
|
||||
fn report(_: &mut impl FnMut(&'static str)) {}
|
||||
}
|
||||
|
||||
pub trait SystemCtor: Debug + Send + Sync + 'static {
|
||||
pub trait SystemCtor: Debug + 'static {
|
||||
type Deps: DepDef;
|
||||
type Instance: System;
|
||||
const NAME: &'static str;
|
||||
@@ -68,7 +68,7 @@ pub trait SystemCtor: Debug + Send + Sync + 'static {
|
||||
fn inst(&self, deps: <Self::Deps as DepDef>::Sat) -> Self::Instance;
|
||||
}
|
||||
|
||||
pub trait DynSystemCtor: Debug + Send + Sync + 'static {
|
||||
pub trait DynSystemCtor: Debug + 'static {
|
||||
fn decl(&self, id: api::SysDeclId) -> api::SystemDecl;
|
||||
fn new_system(&self, new: &api::NewSystem) -> CtedObj;
|
||||
}
|
||||
@@ -91,8 +91,7 @@ impl<T: SystemCtor> DynSystemCtor for T {
|
||||
}
|
||||
|
||||
mod dep_set_tuple_impls {
|
||||
use orchid_base::box_chain;
|
||||
use orchid_base::boxed_iter::BoxedIter;
|
||||
use orchid_base::{BoxedIter, box_chain};
|
||||
use pastey::paste;
|
||||
|
||||
use super::{DepDef, DepSat};
|
||||
|
||||
@@ -11,22 +11,50 @@ use crate::ext_port::ExtPort;
|
||||
/// shutdown.
|
||||
#[cfg(feature = "tokio")]
|
||||
pub async fn tokio_entrypoint(builder: ExtensionBuilder) {
|
||||
use std::cell::RefCell;
|
||||
|
||||
use async_event::Event;
|
||||
use tokio::io::{stderr, stdin, stdout};
|
||||
use tokio::task::{LocalSet, spawn_local};
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
|
||||
let local_set = LocalSet::new();
|
||||
local_set.spawn_local(async {
|
||||
builder.build(ExtPort {
|
||||
input: Box::pin(stdin().compat()),
|
||||
output: Box::pin(stdout().compat_write()),
|
||||
log: Box::pin(stderr().compat_write()),
|
||||
spawn: Rc::new(|fut| {
|
||||
spawn_local(fut);
|
||||
}),
|
||||
});
|
||||
});
|
||||
local_set.await;
|
||||
let cc = Rc::new(Event::new());
|
||||
let c = Rc::new(RefCell::new(1));
|
||||
LocalSet::new()
|
||||
.run_until(async {
|
||||
let counter = (c.clone(), cc.clone());
|
||||
builder
|
||||
.run(ExtPort {
|
||||
input: Box::pin(stdin().compat()),
|
||||
output: Box::pin(stdout().compat_write()),
|
||||
log: Box::pin(stderr().compat_write()),
|
||||
spawn: Rc::new(move |delay, fut| {
|
||||
let (c, cc) = counter.clone();
|
||||
if delay.is_zero() {
|
||||
*c.borrow_mut() += 1;
|
||||
cc.notify_all();
|
||||
spawn_local(async move {
|
||||
fut.await;
|
||||
*c.borrow_mut() -= 1;
|
||||
cc.notify_all();
|
||||
});
|
||||
} else {
|
||||
let at = tokio::time::Instant::now() + delay;
|
||||
spawn_local(async move {
|
||||
tokio::time::sleep_until(at).await;
|
||||
*c.borrow_mut() += 1;
|
||||
cc.notify_all();
|
||||
fut.await;
|
||||
*c.borrow_mut() -= 1;
|
||||
cc.notify_all();
|
||||
});
|
||||
}
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
cc.wait_until(|| (*c.borrow() == 0).then_some(())).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
||||
@@ -8,10 +8,7 @@ use futures::future::{LocalBoxFuture, join_all};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchid_base::interner::{IStr, is};
|
||||
use orchid_base::location::SrcRange;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::tree::{TokTree, Token, TokenVariant};
|
||||
use orchid_base::{IStr, SrcRange, Sym, TokTree, Token, TokenVariant, is};
|
||||
use substack::Substack;
|
||||
use task_local::task_local;
|
||||
use trait_set::trait_set;
|
||||
@@ -20,7 +17,7 @@ use crate::api;
|
||||
use crate::conv::ToExpr;
|
||||
use crate::expr::{BorrowedExprStore, Expr, ExprHandle};
|
||||
use crate::func_atom::{ExprFunc, Fun};
|
||||
use crate::gen_expr::{GExpr, new_atom, sym_ref};
|
||||
use crate::gen_expr::{GExpr, new_atom};
|
||||
|
||||
pub type GenTokTree = TokTree<Expr, GExpr>;
|
||||
pub type GenTok = Token<Expr, GExpr>;
|
||||
@@ -28,7 +25,7 @@ pub type GenTok = Token<Expr, GExpr>;
|
||||
impl TokenVariant<api::Expression> for GExpr {
|
||||
type FromApiCtx<'a> = ();
|
||||
type ToApiCtx<'a> = ();
|
||||
async fn from_api(_: &api::Expression, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||
async fn from_api(_: api::Expression, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||
panic!("Received new expression from host")
|
||||
}
|
||||
async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> api::Expression { self.serialize().await }
|
||||
@@ -36,16 +33,16 @@ impl TokenVariant<api::Expression> for GExpr {
|
||||
|
||||
impl TokenVariant<api::ExprTicket> for Expr {
|
||||
type FromApiCtx<'a> = &'a BorrowedExprStore;
|
||||
async fn from_api(api: &api::ExprTicket, exprs: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||
async fn from_api(api: api::ExprTicket, exprs: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||
// SAFETY: receiving trees from sublexers implies borrowing
|
||||
Expr::from_handle(ExprHandle::borrowed(*api, exprs))
|
||||
Expr::from_handle(ExprHandle::borrowed(api, exprs))
|
||||
}
|
||||
type ToApiCtx<'a> = ();
|
||||
async fn into_api(self, (): &mut Self::ToApiCtx<'_>) -> api::ExprTicket { self.handle().ticket() }
|
||||
}
|
||||
|
||||
pub async fn x_tok(x: impl ToExpr) -> GenTok { GenTok::NewExpr(x.to_gen().await) }
|
||||
pub async fn ref_tok(path: Sym) -> GenTok { GenTok::NewExpr(sym_ref(path)) }
|
||||
pub async fn ref_tok(path: Sym) -> GenTok { GenTok::NewExpr(path.to_gen().await) }
|
||||
|
||||
pub fn lazy(
|
||||
public: bool,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::pin::Pin;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::{AsyncRead, AsyncWrite};
|
||||
use orchid_base::reqnot::{Receipt, RepWriter, ReqHandle, ReqReader};
|
||||
use orchid_base::{Receipt, RepWriter, ReqHandle, ReqReader};
|
||||
|
||||
pub struct TrivialReqCycle<'a> {
|
||||
pub req: &'a [u8],
|
||||
|
||||
Reference in New Issue
Block a user