Compiles again after command subsystem

terrified to start testing
This commit is contained in:
2026-03-27 23:50:58 +01:00
parent 09cfcb1839
commit 0909524dee
75 changed files with 1165 additions and 609 deletions

View File

@@ -1,18 +1,17 @@
use std::any::{Any, TypeId, type_name};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::{self, Debug};
use std::future::Future;
use std::io;
use std::marker::PhantomData;
use std::num::{NonZero, NonZeroU32, NonZeroU64};
use std::num::NonZeroU32;
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};
@@ -20,15 +19,14 @@ use orchid_base::{
FmtCtx, FmtUnit, Format, IStr, OrcErrv, Pos, Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym,
fmt, is, mk_errv, mk_errv_floating, take_first,
};
use task_local::task_local;
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::expr::{Expr, ExprData, ExprHandle, ExprKind};
use crate::gen_expr::{GExpr, IntoGExprStream};
use crate::system::{DynSystemCardExt, cted, sys_id};
use crate::gen_expr::GExpr;
use crate::{
DynSystemCardExt, Expr, ExprData, ExprHandle, ExprKind, OwnedAtom, ToExpr, api, dyn_cted,
get_obj_store, request, 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)]
@@ -114,7 +112,7 @@ impl ForeignAtom {
let mut data = &self.atom.data.0[..];
let value = AtomTypeId::decode_slice(&mut data);
if cfg!(debug_assertions) {
let cted = cted();
let cted = dyn_cted();
let own_inst = cted.inst();
let owner_id = self.atom.owner;
let typ = type_name::<A>();
@@ -189,6 +187,10 @@ pub trait AtomMethod: Coding + InHierarchy {
const NAME: &str;
}
task_local! {
pub(crate) static ATOM_WITHOUT_HANDLE_FINAL_IMPL: Rc<RefCell<Option<Box<dyn Any>>>>;
}
/// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be
/// registered in [Atomic::reg_methods]
pub trait Supports<M: AtomMethod>: Atomic {
@@ -197,25 +199,53 @@ pub trait Supports<M: AtomMethod>: Atomic {
hand: Box<dyn ReqHandle<'a> + '_>,
req: M,
) -> impl Future<Output = io::Result<Receipt<'a>>>;
fn handle_final<'a>(
self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: M,
) -> impl Future<Output = io::Result<Receipt<'a>>> {
async move {
let rcpt = self.handle(hand, req).await;
let _ = ATOM_WITHOUT_HANDLE_FINAL_IMPL.try_with(|cell| cell.replace(Some(Box::new(self))));
rcpt
}
}
// TODO: default-implement the above somehow while calling OwnedAtom::free if
// necessary
}
trait HandleAtomMethod<A> {
fn handle<'a, 'b: 'a>(
&'a self,
atom: &'a A,
req: Box<dyn ReqReader<'b> + 'a>,
reader: Box<dyn ReqReader<'b> + 'a>,
) -> LocalBoxFuture<'a, ()>;
fn handle_final<'a, 'b: 'a>(
&'a self,
atom: A,
reader: Box<dyn ReqReader<'b> + 'a>,
) -> LocalBoxFuture<'a, ()>;
}
struct AtomMethodHandler<M, A>(PhantomData<M>, PhantomData<A>);
impl<M: AtomMethod, A: Supports<M>> HandleAtomMethod<A> for AtomMethodHandler<M, A> {
fn handle<'a, 'b: 'a>(
&'a self,
a: &'a A,
atom: &'a A,
mut reader: Box<dyn ReqReader<'b> + 'a>,
) -> LocalBoxFuture<'a, ()> {
Box::pin(async {
let req = reader.read_req::<M>().await.unwrap();
let _ = Supports::<M>::handle(a, reader.finish().await, req).await.unwrap();
let _ = Supports::<M>::handle(atom, reader.finish().await, req).await.unwrap();
})
}
fn handle_final<'a, 'b: 'a>(
&'a self,
atom: A,
mut reader: Box<dyn ReqReader<'b> + 'a>,
) -> LocalBoxFuture<'a, ()> {
Box::pin(async {
let req = reader.read_req::<M>().await.unwrap();
let _ = Supports::<M>::handle_final(atom, reader.finish().await, req).await.unwrap();
})
}
}
@@ -252,6 +282,20 @@ pub(crate) struct MethodSet<A: Atomic> {
handlers: HashMap<Sym, Rc<dyn HandleAtomMethod<A>>>,
}
impl<A: Atomic> MethodSet<A> {
pub(crate) async fn final_dispatch<'a>(
&self,
atom: A,
key: Sym,
req: Box<dyn ReqReader<'a> + 'a>,
) -> bool {
match self.handlers.get(&key) {
None => false,
Some(handler) => {
handler.handle_final(atom, req).await;
true
},
}
}
pub(crate) async fn dispatch<'a>(
&self,
atom: &'_ A,
@@ -334,75 +378,6 @@ impl<A: AtomicFeatures> Format for TAtom<A> {
pub(crate) struct AtomCtx<'a>(pub &'a [u8], pub Option<api::AtomId>);
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]
@@ -420,7 +395,12 @@ pub trait AtomOps: 'static {
key: Sym,
req: Box<dyn ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, bool>;
fn command<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, CmdResult>;
fn handle_req_ref<'a>(
&'a self,
ctx: AtomCtx<'a>,
key: Sym,
req: Box<dyn ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, bool>;
fn serialize<'a, 'b: 'a>(
&'a self,
ctx: AtomCtx<'a>,
@@ -504,6 +484,6 @@ pub async fn err_exit_failure() -> OrcErrv {
pub(crate) fn resolve_atom_type(atom: &api::Atom) -> (Box<dyn AtomOps>, AtomTypeId, &[u8]) {
let mut data = &atom.data.0[..];
let atid = AtomTypeId::decode_slice(&mut data);
let atom_record = cted().inst().card().ops_by_atid(atid).expect("Unrecognized atom type ID");
let atom_record = dyn_cted().inst().card().ops_by_atid(atid).expect("Unrecognized atom type ID");
(atom_record, atid, data)
}

View File

@@ -20,15 +20,12 @@ use orchid_api_traits::{Decode, Encode, enc_vec};
use orchid_base::{FmtCtx, FmtCtxImpl, FmtUnit, Format, Sym, log, take_first};
use task_local::task_local;
use crate::atom::{
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::{DynSystemCardExt, cted};
use crate::{CmdError, CmdResult, api};
use crate::{
ATOM_WITHOUT_HANDLE_FINAL_IMPL, AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl,
AtomicVariant, DynSystemCardExt, Expr, MethodSet, MethodSetBuilder, ToExpr, api, dyn_cted,
err_not_callable,
};
/// Value of [Atomic::Variant] for a type that implements [OwnedAtom]
pub struct OwnedVariant;
@@ -42,7 +39,7 @@ impl<A: OwnedAtom + Atomic<Variant = OwnedVariant>> AtomicFeaturesImpl<OwnedVari
*id += 1;
api::AtomId(NonZero::new(*id + 1).unwrap())
};
let (typ_id, _) = cted().inst().card().ops::<A>();
let (typ_id, _) = dyn_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));
@@ -121,6 +118,25 @@ impl<A: OwnedAtom> AtomOps for OwnedAtomOps<A> {
AtomCtx(_, id): AtomCtx<'a>,
key: Sym,
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, bool> {
Box::pin(async move {
let a = take_atom(id.unwrap()).await;
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
let cell = Rc::new(RefCell::new(None));
let matched = ATOM_WITHOUT_HANDLE_FINAL_IMPL
.scope(cell.clone(), ms.final_dispatch(*a.as_any().downcast().unwrap(), key, req))
.await;
if let Some(val) = cell.take() {
val.downcast::<A>().unwrap().free().await
}
matched
})
}
fn handle_req_ref<'a>(
&'a self,
AtomCtx(_, id): AtomCtx<'a>,
key: Sym,
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, bool> {
Box::pin(async move {
let a = AtomReadGuard::new(id.unwrap()).await;
@@ -128,9 +144,6 @@ impl<A: OwnedAtom> AtomOps for OwnedAtomOps<A> {
ms.dispatch(a.as_any_ref().downcast_ref().unwrap(), key, req).await
})
}
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<'_, ()> {
Box::pin(async move { take_atom(id.unwrap()).await.dyn_free().await })
}
@@ -252,10 +265,6 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
gcl
}
}
/// 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)) }
}
/// Drop and perform any cleanup. Unlike Rust's [Drop::drop], this is
/// guaranteed to be called
fn free(self) -> impl Future<Output = ()> { async {} }
@@ -294,10 +303,10 @@ fn assert_serializable<T: OwnedAtom>() {
pub(crate) trait DynOwnedAtom: DynClone + 'static {
fn as_any_ref(&self) -> &dyn Any;
fn as_any(self: Box<Self>) -> Box<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, CmdResult>;
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit>;
fn dyn_serialize<'a>(
@@ -307,6 +316,7 @@ pub(crate) trait DynOwnedAtom: DynClone + 'static {
}
impl<T: OwnedAtom> DynOwnedAtom for T {
fn as_any_ref(&self) -> &dyn Any { self }
fn as_any(self: Box<Self>) -> Box<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()
}
@@ -316,7 +326,6 @@ impl<T: OwnedAtom> DynOwnedAtom for T {
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr> {
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()

View File

@@ -3,19 +3,16 @@ use std::future::Future;
use std::pin::Pin;
use async_once_cell::OnceCell;
use futures::AsyncWrite;
use futures::future::LocalBoxFuture;
use futures::{AsyncWrite, FutureExt};
use orchid_api_traits::{Coding, enc_vec};
use orchid_base::{FmtUnit, Sym, log};
use crate::atom::{
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::{DynSystemCardExt, cted};
use crate::{CmdResult, api};
use crate::{
AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, AtomicVariant, DynSystemCardExt, Expr,
MethodSet, MethodSetBuilder, api, dyn_cted, err_not_callable,
};
/// Value of [Atomic::Variant] for a type that implements [ThinAtom]
pub struct ThinVariant;
@@ -23,7 +20,7 @@ impl AtomicVariant for ThinVariant {}
impl<A: ThinAtom + Atomic<Variant = ThinVariant>> AtomicFeaturesImpl<ThinVariant> for A {
fn _factory(self) -> AtomFactory {
AtomFactory::new(async move || {
let (id, _) = cted().inst().card().ops::<A>();
let (id, _) = dyn_cted().inst().card().ops::<A>();
let mut buf = enc_vec(&id);
self.encode_vec(&mut buf);
api::LocalAtom { drop: None, data: api::AtomData(buf) }
@@ -63,8 +60,13 @@ impl<T: ThinAtom> AtomOps for ThinAtomOps<T> {
ms.dispatch(&T::decode_slice(&mut &buf[..]), key, req).await
})
}
fn command<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, CmdResult> {
async move { T::decode_slice(&mut &buf[..]).command().await }.boxed_local()
fn handle_req_ref<'a>(
&'a self,
ctx: AtomCtx<'a>,
key: Sym,
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, bool> {
self.handle_req(ctx, key, req)
}
fn serialize<'a, 'b: 'a>(
&'a self,
@@ -99,10 +101,6 @@ pub trait ThinAtom: Atomic<Data = Self> + Atomic<Variant = ThinVariant> + Coding
async move { bot(err_not_callable(&self.print().await).await) }
}
#[allow(unused_variables)]
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> {
async { format!("ThinAtom({})", type_name::<Self>()).into() }
}

View File

@@ -4,9 +4,7 @@ use std::time::Duration;
use futures::future::LocalBoxFuture;
use orchid_base::future_to_vt;
use crate::api;
use crate::entrypoint::ExtensionBuilder;
use crate::ext_port::ExtPort;
use crate::{ExtPort, ExtensionBuilder, api};
pub type ExtCx = api::binary::ExtensionContext;

View File

@@ -0,0 +1,60 @@
use std::borrow::Cow;
use dyn_clone::DynClone;
use futures::future::LocalBoxFuture;
use never::Never;
use orchid_base::{Receipt, ReqHandle, ReqHandleExt};
use trait_set::trait_set;
use crate::gen_expr::{GExpr, new_atom};
use crate::std_reqs::RunCommand;
use crate::{Atomic, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr};
trait_set! {
pub trait ClonableAsyncFnOnceDyn = FnOnce() -> LocalBoxFuture<'static, Option<GExpr>> + DynClone;
}
pub struct CmdAtom(Box<dyn ClonableAsyncFnOnceDyn>);
impl Clone for CmdAtom {
fn clone(&self) -> Self { Self(dyn_clone::clone_box(&*self.0)) }
}
impl Atomic for CmdAtom {
type Data = ();
type Variant = OwnedVariant;
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<RunCommand>() }
}
impl Supports<RunCommand> for CmdAtom {
async fn handle<'a>(
&self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: RunCommand,
) -> std::io::Result<Receipt<'a>> {
Self(dyn_clone::clone_box(&*self.0)).handle_final(hand, req).await
}
async fn handle_final<'a>(
self,
hand: Box<dyn ReqHandle<'a> + '_>,
req: RunCommand,
) -> std::io::Result<Receipt<'a>> {
let reply = (self.0)().await;
match reply {
None => hand.reply(&req, &None).await,
Some(next) => hand.reply(&req, &Some(next.serialize().await)).await,
}
}
}
impl OwnedAtom for CmdAtom {
type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
}
pub fn cmd<R: ToExpr>(f: impl AsyncFnOnce() -> Option<R> + Clone + 'static) -> GExpr {
new_atom(CmdAtom(Box::new(|| {
Box::pin(async {
match f().await {
None => None,
Some(r) => Some(r.to_gen().await),
}
})
})))
}

View File

@@ -2,19 +2,20 @@ use std::future::Future;
use std::pin::Pin;
use dyn_clone::DynClone;
use futures::future::FusedFuture;
use never::Never;
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};
use crate::{AtomicFeatures, Expr, ExprKind, ForeignAtom, TAtom};
/// 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
/// Values that may be converted from certain specific Orchid expressions
pub trait TryFromExpr: Sized {
/// 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
fn try_from_expr(expr: Expr) -> impl Future<Output = OrcRes<Self>>;
}
@@ -58,6 +59,9 @@ impl<A: AtomicFeatures> TryFromExpr for TAtom<A> {
/// Values that are convertible to an Orchid expression. This could mean that
/// the value owns an [Expr] or it may involve more complex operations
///
/// [ToExpr] is also implemented for [orchid_base::Sym] where it converts to a
/// reference to the constant by that name
pub trait ToExpr {
/// Inline the value in an expression returned from a function or included in
/// the const tree returned by [crate::System::env]
@@ -69,6 +73,8 @@ pub trait ToExpr {
}
}
/// A wrapper for a future that implements [ToExpr]
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ToExprFuture<F>(pub F);
impl<F: Future<Output: ToExpr>> ToExpr for ToExprFuture<F> {
@@ -78,6 +84,9 @@ impl<F: Future<Output: ToExpr>> ToExpr for ToExprFuture<F> {
self.0.await.to_expr().await
}
}
impl<F: FusedFuture> FusedFuture for ToExprFuture<F> {
fn is_terminated(&self) -> bool { self.0.is_terminated() }
}
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> {

View File

@@ -9,11 +9,8 @@ use futures::{FutureExt, SinkExt, StreamExt};
use never::Never;
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, lam, new_atom, seq};
use crate::{Atomic, Expr, OwnedAtom, OwnedVariant, ToExpr, TryFromExpr};
enum Command {
Execute(GExpr, Sender<Expr>),
@@ -30,7 +27,7 @@ impl BuilderCoroutine {
pub async fn run(self) -> GExpr {
let cmd = self.0.receiver.lock().await.next().await;
match cmd {
None => panic!("Before the stream ends, we should have gotten a Halt"),
None => panic!("Exec handle dropped and coroutine blocked instead of returning"),
Some(Command::Halt(expr)) => expr,
Some(Command::Execute(expr, reply)) =>
call(lam::<0>(seq(arg(0), call(new_atom(Replier { reply, builder: self }), arg(0)))), expr)
@@ -40,7 +37,7 @@ impl BuilderCoroutine {
}
#[derive(Clone)]
pub struct Replier {
pub(crate) struct Replier {
reply: Sender<Expr>,
builder: BuilderCoroutine,
}
@@ -52,12 +49,14 @@ impl OwnedAtom for Replier {
type Refs = Never;
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
async fn call(mut self, arg: Expr) -> impl ToExpr {
self.reply.send(arg).await.expect("What the heck");
self.reply.send(arg).await.expect("Resolution request dropped after sending");
std::mem::drop(self.reply);
self.builder.run().await
}
}
/// A long-lived async context that can yield to the executor. The expression
/// representing an in-progress exec block is not serializable.
pub async fn exec<R: ToExpr>(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + 'static) -> GExpr {
let (cmd_snd, cmd_recv) = channel(0);
let halt =
@@ -70,8 +69,11 @@ pub async fn exec<R: ToExpr>(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R +
static WEIRD_DROP_ERR: &str = "Coroutine dropped while we are being polled somehow";
/// The handle an [exec] callback uses to yield to the executor
pub struct ExecHandle<'a>(Sender<Command>, PhantomData<&'a ()>);
impl ExecHandle<'_> {
/// Yield to the executor by resolving to an expression that normalizes the
/// value and then calls the continuation of the body with the result.
pub async fn exec<T: TryFromExpr>(&mut self, val: impl ToExpr) -> OrcRes<T> {
let (reply_snd, mut reply_recv) = channel(1);
self.0.send(Command::Execute(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR);

View File

@@ -22,21 +22,16 @@ use orchid_base::{
use substack::Substack;
use task_local::task_local;
use crate::atom::{AtomCtx, AtomTypeId, resolve_atom_type};
use crate::atom_owned::{take_atom, with_obj_store};
use crate::expr::{BorrowedExprStore, Expr, ExprHandle};
use crate::ext_port::ExtPort;
use crate::func_atom::with_funs_ctx;
use crate::interner::new_interner;
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::{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};
use crate::{
AtomCtx, AtomTypeId, BorrowedExprStore, CtedObj, DynSystemCardExt, DynSystemCtor, Expr,
ExprHandle, ExtPort, LexContext, PTokTree, ParsCtx, SysCtx, SystemCtor, api, dyn_cted,
ekey_cascade, ekey_not_applicable, get_const, linev_into_api, resolve_atom_type, take_atom,
with_funs_ctx, with_obj_store, with_parsed_const_ctx, with_refl_roots, with_sys,
};
task_local::task_local! {
static CLIENT: Rc<dyn Client>;
@@ -44,21 +39,28 @@ task_local::task_local! {
}
fn get_client() -> Rc<dyn Client> { CLIENT.get() }
/// Do not expect any more requests or notifications, exit once all pending
/// requests settle
pub async fn exit() {
let cx = CTX.get().borrow_mut().take();
cx.unwrap().exit().await.unwrap()
}
/// Sent the client used for global [request] and [notify] functions within the
/// Set the client used for global [request] and [notify] functions within the
/// runtime of this future
pub async fn with_comm<F: Future>(c: Rc<dyn Client>, ctx: CommCtx, fut: F) -> F::Output {
CLIENT.scope(c, CTX.scope(Rc::new(RefCell::new(Some(ctx))), fut)).await
}
task_local! {
pub static MUTE_REPLY: ();
static MUTE_REPLY: ();
}
/// Silence replies within this block even if the `msg` log channel is active to
/// prevent excessive log noise.
pub async fn mute_reply<F: Future>(f: F) -> F::Output { MUTE_REPLY.scope((), f).await }
/// Send a request through the global client's [ClientExt::request]
pub async fn request<T: Request + UnderRoot<Root = api::ExtHostReq>>(t: T) -> T::Response {
let response = get_client().request(t).await.unwrap();
@@ -73,7 +75,7 @@ pub async fn notify<T: UnderRoot<Root = api::ExtHostNotif>>(t: T) {
get_client().notify(t).await.unwrap()
}
pub struct SystemRecord {
struct SystemRecord {
cted: CtedObj,
}
@@ -88,6 +90,7 @@ async fn with_sys_record<F: Future>(id: api::SysId, fut: F) -> F::Output {
with_sys(SysCtx(id, cted), fut).await
}
/// Context that can be attached to a [Future] using [task_local]
pub trait ContextModifier: 'static {
fn apply<'a>(self: Box<Self>, fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()>;
}
@@ -108,12 +111,22 @@ task_local! {
Rc<dyn Fn(Duration, LocalBoxFuture<'static, Box<dyn Any>>) -> Box<dyn DynTaskHandle> + 'static>
}
/// Handle for a task that is not associated with a particular pending request
/// or past notification
pub struct TaskHandle<T>(Box<dyn DynTaskHandle>, PhantomData<T>);
impl<T: 'static> TaskHandle<T> {
/// Immediately stop working on the task. Unlike in Tokio's abort, this is a
/// guarantee
pub fn abort(self) { self.0.abort(); }
/// Stop working on the task and return the nested future. The distinction
/// between this and waiting until the task is complete without reparenting it
/// is significant for the purpose of [task_local] context
pub async fn join(self) -> T { *self.0.join().await.downcast().unwrap() }
}
/// Spawn a future that is not associated with a pending request or a past
/// notification. Pending tasks are cancelled and dropped when the extension
/// exits
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)
@@ -125,24 +138,37 @@ impl DynTaskHandle for Handle<Box<dyn Any>> {
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>> { Box::pin(Self::join(*self)) }
}
/// A new Orchid extension as specified in loaders. An extension is a unit of
/// distribution and its name serves for debugging purposes primarily. In
/// contrast, [SystemCtor] is a unit of features of which [ExtensionBuilder] may
/// contain multiple
pub struct ExtensionBuilder {
pub name: &'static str,
pub systems: Vec<Box<dyn DynSystemCtor>>,
pub context: Vec<Box<dyn ContextModifier>>,
}
impl ExtensionBuilder {
/// Create a new extension
pub fn new(name: &'static str) -> Self { Self { name, systems: Vec::new(), context: Vec::new() } }
/// Add a system to the extension
pub fn system(mut self, ctor: impl SystemCtor) -> Self {
self.systems.push(Box::new(ctor) as Box<_>);
self
}
/// Add some [task_local] state to the extension. Bear in mind that distinct
/// [crate::System] instances should not visibly affect each other
pub fn add_context(&mut self, fun: impl ContextModifier) {
self.context.push(Box::new(fun) as Box<_>);
}
/// Builder form of [Self::add_context]
pub fn context(mut self, fun: impl ContextModifier) -> Self {
self.add_context(fun);
self
}
/// Start the extension on a message channel, blocking the task until the peer
/// on the other side drops the extension. Extension authors would typically
/// pass the prepared builder into some function that is responsible for
/// managing the [ExtPort]
pub async fn run(mut self, mut ctx: ExtPort) {
self.add_context(with_funs_ctx);
self.add_context(with_parsed_const_ctx);
@@ -246,7 +272,7 @@ impl ExtensionBuilder {
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;
let _ = dyn_cted().inst().dyn_request(Box::new(req)).await;
handle.reply(fwd_tok, &reply).await
})
.await
@@ -260,7 +286,7 @@ impl ExtensionBuilder {
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();
let lexers = dyn_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());
@@ -292,7 +318,7 @@ impl ExtensionBuilder {
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 parsers = dyn_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;
@@ -339,14 +365,22 @@ impl ExtensionBuilder {
},
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;
api::AtomReq::FinalFwded(fwded) => {
let api::FinalFwded(_, 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::FwdedRef(fwded) => {
let api::FinalFwded(_, 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_ref(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);
@@ -365,8 +399,6 @@ impl ExtensionBuilder {
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
@@ -380,7 +412,7 @@ impl ExtensionBuilder {
.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))
let nfo = (dyn_cted().inst().card().ops_by_atid(id))
.expect("Deserializing atom with invalid ID");
handle.reply(&deser, &nfo.deserialize(read, &refs).await).await
})

View File

@@ -6,23 +6,28 @@ use std::thread::panicking;
use async_once_cell::OnceCell;
use derive_destructure::destructure;
use futures::future::join_all;
use hashbrown::HashSet;
use orchid_base::{FmtCtx, FmtUnit, Format, OrcErrv, Pos, stash};
use crate::api;
use crate::atom::ForeignAtom;
use crate::entrypoint::{notify, request};
use crate::gen_expr::{GExpr, GExprKind};
use crate::system::sys_id;
use crate::{ForeignAtom, api, notify, request, sys_id};
/// Handle for a lifetime associated with an [ExprHandle], such as a function
/// call. Can be passed into [ExprHandle::borrowed] as an optimization over
/// [ExprHandle::from_ticket]
///
/// # Panics
///
/// The [Drop] of this type panics by default unless the stack is already
/// unwinding. You need to make sure you dispose of it by calling
/// [Self::dispose].
pub struct BorrowedExprStore(RefCell<Option<HashSet<Rc<ExprHandle>>>>);
impl BorrowedExprStore {
pub(crate) fn new() -> Self { Self(RefCell::new(Some(HashSet::new()))) }
pub async fn dispose(self) {
let elements = self.0.borrow_mut().take().unwrap();
for handle in elements {
handle.on_borrow_expire().await
}
let set = self.0.borrow_mut().take().expect("Double dispose of BorrowedExprStore!");
join_all(set.into_iter().map(ExprHandle::on_borrow_expire)).await;
}
}
impl Drop for BorrowedExprStore {
@@ -33,6 +38,10 @@ impl Drop for BorrowedExprStore {
}
}
/// A RAII wrapper over an [api::ExprTicket]. Extension authors usually use
/// [Expr] for all purposes as this type does not deal with the details of the
/// expression associated with the ticket, it merely ensures that [api::Acquire]
/// and [api::Release] are sent at appropriate times.
#[derive(destructure, PartialEq, Eq, Hash)]
pub struct ExprHandle(api::ExprTicket);
impl ExprHandle {
@@ -72,7 +81,7 @@ impl ExprHandle {
/// [ExprHandle::borrowed] or [ExprHandle::from_ticket]
pub fn ticket(&self) -> api::ExprTicket { self.0 }
async fn send_acq(&self) { notify(api::Acquire(sys_id(), self.0)).await }
/// If this is the last one reference, do nothing, otherwise send an Acquire
/// If this is the last reference, do nothing, otherwise send an Acquire
pub async fn on_borrow_expire(self: Rc<Self>) { self.serialize().await; }
/// Drop the handle and get the ticket without a release notification.
/// Use this with messages that imply ownership transfer. This function is
@@ -97,13 +106,20 @@ impl Drop for ExprHandle {
}
}
/// A smart object that keeps an expression alive in the interpreter until all
/// references are dropped and provides information about that expression. These
/// can be stored in any pattern, but care must be taken as adding new ones into
/// a structure that is already visible to the interpreter can easily cause a
/// memory leak.
#[derive(Clone, Debug, destructure)]
pub struct Expr {
handle: Rc<ExprHandle>,
data: Rc<OnceCell<ExprData>>,
}
impl Expr {
/// Wrap a handle in order to retrieve details about it
pub fn from_handle(handle: Rc<ExprHandle>) -> Self { Self { handle, data: Rc::default() } }
/// Wrap a handle the details of which are already known
pub fn from_data(handle: Rc<ExprHandle>, d: ExprData) -> Self {
Self { handle, data: Rc::new(OnceCell::from(d)) }
}
@@ -113,6 +129,8 @@ impl Expr {
pub async fn deserialize(tk: api::ExprTicket) -> Self {
Self::from_handle(ExprHandle::deserialize(tk))
}
/// Fetch the details of this expression via [api::Inspect] if not already
/// known, and return them
pub async fn data(&self) -> &ExprData {
(self.data.get_or_init(async {
let details = request(api::Inspect { target: self.handle.ticket() }).await;
@@ -127,15 +145,19 @@ impl Expr {
}))
.await
}
/// Attempt to downcast this to a [ForeignAtom]
pub async fn atom(self) -> Result<ForeignAtom, Self> {
match self.data().await {
ExprData { kind: ExprKind::Atom(atom), .. } => Ok(atom.clone()),
_ => Err(self),
}
}
/// Obtain code location info associated with this expression for logging.
pub async fn pos(&self) -> Pos { self.data().await.pos.clone() }
/// Clone out the handle for this expression
pub fn handle(&self) -> Rc<ExprHandle> { self.handle.clone() }
/// Wrap this expression in a [GExpr] synchronously as an escape hatch.
/// Otherwise identical to this type's [crate::ToExpr] impl
pub fn slot(&self) -> GExpr {
GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) }
}
@@ -161,15 +183,23 @@ impl Hash for Expr {
fn hash<H: Hasher>(&self, state: &mut H) { self.handle.hash(state); }
}
/// Information about an expression
#[derive(Clone, Debug)]
pub struct ExprData {
/// Source code location data associated with the expression for debug logging
pub pos: Pos,
/// Limited information on the value available to extensions
pub kind: ExprKind,
}
/// All that is visible about a runtime value to extensions. This
/// information is limited for the sake of extensibility
#[derive(Clone, Debug)]
pub enum ExprKind {
/// An atom, local or foreign
Atom(ForeignAtom),
/// A runtime error
Bottom(OrcErrv),
/// Some other value, possibly normalizes to one of the above
Opaque,
}

View File

@@ -15,14 +15,10 @@ use orchid_base::{FmtCtx, FmtUnit, OrcRes, Pos, Sym, clone};
use task_local::task_local;
use trait_set::trait_set;
use crate::api;
use crate::atom::Atomic;
use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
use crate::conv::ToExpr;
use crate::coroutine_exec::{ExecHandle, exec};
use crate::expr::Expr;
use crate::gen_expr::{GExpr, new_atom};
use crate::system::sys_id;
use crate::{
Atomic, DeserializeCtx, ExecHandle, Expr, OwnedAtom, OwnedVariant, ToExpr, api, exec, sys_id,
};
trait_set! {
trait FunCB = Fn(Vec<Expr>) -> LocalBoxFuture<'static, OrcRes<GExpr>> + 'static;
@@ -32,6 +28,7 @@ task_local! {
static ARGV: Vec<Expr>;
}
/// Wihtin an [ExprFunc]'s body, access a raw argument by index
pub fn get_arg(idx: usize) -> Expr {
ARGV
.try_with(|argv| {
@@ -41,16 +38,26 @@ pub fn get_arg(idx: usize) -> Expr {
.expect("get_arg called outside ExprFunc")
}
/// Find the number of arguments accepted by this [ExprFunc]
pub fn get_argc() -> usize {
ARGV.try_with(|argv| argv.len()).expect("get_arg called outside ExprFunc")
}
/// Retrieve the code locations associated with specific arguments by index.
/// This is intended to be the last argument to [orchid_base::mk_errv]
pub async fn get_arg_posv(idxes: impl IntoIterator<Item = usize>) -> impl Iterator<Item = Pos> {
let args = (ARGV.try_with(|argv| idxes.into_iter().map(|i| &argv[i]).cloned().collect_vec()))
.expect("get_arg_posv called outside ExprFunc");
join_all(args.iter().map(|expr| expr.pos())).await.into_iter()
}
/// A Rust lambda that can be placed into an Orchid atom. This requires that
///
/// - the lambda is [Clone] and `'static`
/// - All of its arguments are [crate::TryFromExpr]
/// - Its return value is [crate::ToExpr]
/// - For the sake of compilation time, at present the trait is only implemented
/// for up to 6 arguments
pub trait ExprFunc<I, O>: Clone + 'static {
fn argtyps() -> &'static [TypeId];
fn apply<'a>(&self, hand: ExecHandle<'a>, v: Vec<Expr>) -> impl Future<Output = OrcRes<GExpr>>;
@@ -152,13 +159,14 @@ impl OwnedAtom for Fun {
/// An Atom representing a partially applied native lambda. These are not
/// serializable.
///
/// See [Fun] for the serializable variant
/// See [crate::fun] for the serializable variant
#[derive(Clone)]
pub struct Lambda {
args: Vec<Expr>,
record: FunRecord,
}
impl Lambda {
/// Embed a lambda in an Orchid expression
pub fn new<I, O, F: ExprFunc<I, O>>(f: F) -> Self {
Self { args: vec![], record: process_args(f) }
}

View File

@@ -7,16 +7,15 @@ use orchid_base::{
FmtCtx, FmtUnit, Format, OrcErr, OrcErrv, Pos, Sym, Variants, match_mapping, tl_cache,
};
use crate::api;
use crate::atom::{AtomFactory, AtomicFeatures};
use crate::conv::{ToExpr, ToExprFuture};
use crate::entrypoint::request;
use crate::expr::Expr;
use crate::system::sys_id;
use crate::{AtomFactory, AtomicFeatures, Expr, ToExpr, ToExprFuture, api, request, sys_id};
/// Newly generated AST. Values of this type should not typically be constructed
/// manually but through the helpers in this module
#[derive(Clone, Debug)]
pub struct GExpr {
/// AST node type
pub kind: GExprKind,
/// Code location associated with the expression for debugging purposes
pub pos: Pos,
}
impl GExpr {
@@ -38,7 +37,10 @@ impl GExpr {
}
}
}
/// Reassign location information. The typical default is [Pos::Inherit]
pub fn at(self, pos: Pos) -> Self { GExpr { pos, kind: self.kind } }
/// Send the expression to the interpreter to be compiled and to become
/// shareable across extensions
pub async fn create(self) -> Expr {
Expr::deserialize(request(api::Create(sys_id(), self.serialize().await)).await).await
}
@@ -49,20 +51,36 @@ impl Format for GExpr {
}
}
/// AST nodes recognized by the interpreter
#[derive(Clone, Debug)]
pub enum GExprKind {
/// Function call
Call(Box<GExpr>, Box<GExpr>),
/// Lambda expression. Argument numbers are matched when equal
Lambda(u64, Box<GExpr>),
/// Slot for a lambda argument
Arg(u64),
/// The second expression is only valid after the first one had already been
/// fully normalized. The main use case is the pattern `Lambda(0, Seq(0,
/// Call(foo, 0)))` where foo is an atom that attempts to downcast its
/// argument.
Seq(Box<GExpr>, Box<GExpr>),
/// A reference to a constant from the shared constant tree. It is best to
/// mark the system that provides named constants as a dependency, but this is
/// not required
Const(Sym),
/// A newly created atom. Since at this point the atom needs to be registered
/// inside the extension but doesn't yet have an [api::ExprTicket], atoms need
/// their own [api::Atom::drop] if they have an identity
#[allow(private_interfaces)]
NewAtom(AtomFactory),
/// An expression previously registered or coming from outside the extension
Slot(Expr),
/// A runtime error
Bottom(OrcErrv),
}
impl GExprKind {
pub async fn serialize(self) -> api::ExpressionKind {
async fn serialize(self) -> api::ExpressionKind {
match_mapping!(self, Self => api::ExpressionKind {
Call(
f => Box::new(f.serialize().await),
@@ -117,6 +135,8 @@ impl ToExpr for Sym {
/// Creates an expression from a new atom that we own.
pub fn new_atom<A: AtomicFeatures>(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.factory())) }
/// An expression which is only valid if a number of dependencies had already
/// been normalized
pub fn seq(
deps: impl IntoGExprStream,
val: impl ToExpr,
@@ -135,17 +155,24 @@ pub fn seq(
})
}
/// Argument bound by an enclosing [lam] or [dyn_lambda]
pub fn arg(n: u64) -> GExpr { inherit(GExprKind::Arg(n)) }
/// A lambda expression. The difference from [dyn_lambda] is purely aesthetic
pub fn lam<const N: u64>(b: impl ToExpr) -> ToExprFuture<impl Future<Output = GExpr>> {
dyn_lambda(N, b)
}
/// A lambda expression. The difference from [lam] is purely aesthetic
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))) })
}
/// one or more items that are convertible to expressions. In practice, a
/// [ToExpr], [Vec<GExpr>], or a tuple of types that all implement [ToExpr]. For
/// compilation performance, the tuple's arity may not be more than 6
pub trait IntoGExprStream {
/// Convert each item to an expression and return them
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr>;
}
impl<T: ToExpr> IntoGExprStream for T {
@@ -184,6 +211,7 @@ mod tuple_impls {
tuple_impl!(A B C D E F);
}
/// Call a (curried) function
pub fn call(
f: impl ToExpr,
argv: impl IntoGExprStream,
@@ -195,6 +223,7 @@ pub fn call(
})
}
/// Call a function on a dynamic number of arguments
pub fn call_v(
f: impl ToExpr,
argv: impl IntoIterator<Item: ToExpr>,
@@ -208,6 +237,7 @@ pub fn call_v(
})
}
/// A runtime error
pub fn bot(ev: impl IntoIterator<Item = OrcErr>) -> GExpr {
inherit(GExprKind::Bottom(OrcErrv::new(ev).unwrap()))
}

View File

@@ -5,8 +5,7 @@ use itertools::Itertools;
use orchid_base::local_interner::{Int, StrBranch, StrvBranch};
use orchid_base::{IStr, IStrv, InternerSrv};
use crate::api;
use crate::entrypoint::{MUTE_REPLY, request};
use crate::{api, mute_reply, request};
#[derive(Default)]
struct ExtInterner {
@@ -17,9 +16,8 @@ impl InternerSrv for ExtInterner {
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr> {
match self.str.i(v) {
Ok(i) => Box::pin(ready(i)),
Err(e) => Box::pin(async {
e.set_if_empty(MUTE_REPLY.scope((), request(api::InternStr(v.to_owned()))).await)
}),
Err(e) =>
Box::pin(async { e.set_if_empty(mute_reply(request(api::InternStr(v.to_owned()))).await) }),
}
}
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> {
@@ -47,4 +45,4 @@ impl InternerSrv for ExtInterner {
}
}
pub fn new_interner() -> Rc<dyn InternerSrv> { Rc::<ExtInterner>::default() }
pub(crate) fn new_interner() -> Rc<dyn InternerSrv> { Rc::<ExtInterner>::default() }

View File

@@ -7,27 +7,30 @@ use futures::FutureExt;
use futures::future::LocalBoxFuture;
use orchid_base::{IStr, OrcErrv, OrcRes, Pos, SrcRange, Sym, is, mk_errv};
use crate::api;
use crate::entrypoint::request;
use crate::expr::BorrowedExprStore;
use crate::parser::PTokTree;
use crate::tree::GenTokTree;
use crate::{BorrowedExprStore, PTokTree, api, request};
pub async fn ekey_cascade() -> IStr { is("An error cascading from a recursive call").await }
pub async fn ekey_not_applicable() -> IStr {
pub(crate) async fn ekey_cascade() -> IStr { is("An error cascading from a recursive call").await }
pub(crate) async fn ekey_not_applicable() -> IStr {
is("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await
}
const MSG_INTERNAL_ERROR: &str = "This error is a sentinel for the extension library.\
it should not be emitted by the extension.";
pub async fn err_cascade() -> OrcErrv {
pub(crate) async fn err_cascade() -> OrcErrv {
mk_errv(ekey_cascade().await, MSG_INTERNAL_ERROR, [Pos::None])
}
/// Return this error if your lexer can determine that it is not applicable to
/// this piece of syntax. This error will not be raised if another lexer
/// matches, or if the piece of matched syntax is found to be valid until
/// runtime
pub async fn err_not_applicable() -> OrcErrv {
mk_errv(ekey_not_applicable().await, MSG_INTERNAL_ERROR, [Pos::None])
}
/// Object passed to lexers for recursion and position-related convenience
/// methods
pub struct LexContext<'a> {
pub(crate) exprs: &'a BorrowedExprStore,
pub text: &'a IStr,
@@ -36,7 +39,7 @@ pub struct LexContext<'a> {
pub(crate) src: Sym,
}
impl<'a> LexContext<'a> {
pub fn new(
pub(crate) fn new(
exprs: &'a BorrowedExprStore,
text: &'a IStr,
id: api::ParsId,
@@ -45,7 +48,11 @@ impl<'a> LexContext<'a> {
) -> Self {
Self { exprs, id, pos, src, text }
}
/// The logical path of the source file, also the path of the file's root
/// module
pub fn src(&self) -> &Sym { &self.src }
/// Lex an interpolated expression of some kind
///
/// This function returns [PTokTree] because it can never return
/// [orchid_base::Token::NewExpr]. You can use
/// [crate::parser::p_tree2gen] to convert this to [crate::tree::GenTokTree]
@@ -58,17 +65,23 @@ impl<'a> LexContext<'a> {
let tree = PTokTree::from_api(lx.tree, &mut { self.exprs }, &mut (), &self.src).await;
Ok((&self.text[lx.pos as usize..], tree))
}
/// Find the index of a cursor given the remaining, not-yet-consumed text
pub fn pos(&self, tail: &'a str) -> u32 { (self.text.len() - tail.len()) as u32 }
/// Convenience method to find the source position of a token given the text
/// it was found in and the text after it was parsed.
pub fn pos_tt(&self, tail_with: &'a str, tail_without: &'a str) -> SrcRange {
SrcRange::new(self.pos(tail_with)..self.pos(tail_without), &self.src)
}
/// Convenience method to find the source position of a token given its length
/// and the remaining text afterwards. The length can be any number type but
/// must convert to a u32 without errors
pub fn pos_lt(&self, len: impl TryInto<u32, Error: fmt::Debug>, tail: &'a str) -> SrcRange {
SrcRange::new(self.pos(tail) - len.try_into().unwrap()..self.pos(tail), &self.src)
}
}
/// One or more tokens returned by the parser. In practice, [GenTokTree],
/// `Vec<GenTokTree>`, or `[GenTokTree; usize]`
pub trait LexedData {
fn into_vec(self) -> Vec<GenTokTree>;
}
@@ -82,16 +95,26 @@ impl<const N: usize> LexedData for [GenTokTree; N] {
fn into_vec(self) -> Vec<GenTokTree> { self.to_vec() }
}
/// A lexer plugin to extend the syntax of Orchid
pub trait Lexer: Debug + Send + Sync + Sized + Default + 'static {
/// As an optimization, your lexer will only receive snippets that start with
/// a character included in one of these ranges. If you have a multi-character
/// discriminator, include all possible starting chars in this and return
/// [err_not_applicable] if the entire discriminator was not found
const CHAR_FILTER: &'static [RangeInclusive<char>];
/// Attempt to lex some custom syntax from the start of the tail string.
/// Return the remaining text and the lexed tokens.
fn lex<'a>(
tail: &'a str,
lctx: &'a LexContext<'a>,
) -> impl Future<Output = OrcRes<(&'a str, impl LexedData)>>;
}
/// Type-erased [Lexer]
pub trait DynLexer: Debug + Send + Sync + 'static {
/// Type-erased [Lexer::CHAR_FILTER]
fn char_filter(&self) -> &'static [RangeInclusive<char>];
/// Type-erased [Lexer::lex]
fn lex<'a>(
&self,
tail: &'a str,
@@ -110,4 +133,6 @@ impl<T: Lexer> DynLexer for T {
}
}
/// Type-erased instance of a lexer that is returned by
/// [crate::System::lexers]
pub type LexerObj = &'static dyn DynLexer;

View File

@@ -3,27 +3,44 @@ use orchid_api as api;
mod atom;
pub use atom::*;
mod cmd_atom;
pub use cmd_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;
pub mod entrypoint;
pub mod expr;
pub mod ext_port;
pub mod func_atom;
mod conv;
pub use conv::*;
mod coroutine_exec;
pub use coroutine_exec::*;
mod entrypoint;
pub use entrypoint::*;
mod expr;
pub use expr::*;
mod ext_port;
pub use ext_port::*;
mod func_atom;
pub use func_atom::*;
pub mod gen_expr;
pub mod interner;
pub mod lexer;
pub mod logger;
pub mod other_system;
pub mod parser;
pub mod reflection;
pub mod stream_reqs;
pub mod system;
pub mod system_ctor;
pub mod tokio;
mod interner;
mod lexer;
pub use lexer::*;
mod logger;
mod other_system;
pub use other_system::*;
mod parser;
pub use parser::*;
mod reflection;
pub use reflection::*;
pub mod std_reqs;
mod system;
pub use system::*;
mod system_ctor;
pub use system_ctor::*;
mod system_card;
pub use system_card::*;
mod tokio;
pub use tokio::*;
pub mod tree;
mod trivial_req;

View File

@@ -7,10 +7,9 @@ use futures::future::LocalBoxFuture;
use hashbrown::HashMap;
use orchid_base::{LogWriter, Logger, is};
use crate::api;
use crate::entrypoint::notify;
use crate::{api, notify};
pub struct LogWriterImpl {
pub(crate) struct LogWriterImpl {
category: String,
strat: api::LogStrategy,
}
@@ -33,7 +32,7 @@ impl LogWriter for LogWriterImpl {
}
#[derive(Clone)]
pub struct LoggerImpl {
pub(crate) struct LoggerImpl {
default: Option<api::LogStrategy>,
routing: HashMap<String, api::LogStrategy>,
}

View File

@@ -1,25 +1,52 @@
use crate::api;
use crate::system::{DynSystemCard, SystemCard};
use std::any::TypeId;
use orchid_api_traits::{Decode, Encode, Request};
use crate::system::{dyn_cted, sys_id};
use crate::{DynSystemCard, SystemCard, api, request};
/// A reference to an instance of an alternate system, passed to
/// [crate::SystemCtor::inst] for dependencies. It can be accepted by functions
/// that call [sys_req] to prove a dependency relationship.
#[derive(Debug)]
pub struct SystemHandle<C: SystemCard> {
pub(crate) card: C,
pub(crate) _card: C,
pub(crate) id: api::SysId,
}
impl<C: SystemCard> SystemHandle<C> {
pub(crate) fn new(id: api::SysId) -> Self { Self { card: C::default(), id } }
pub(crate) fn new(id: api::SysId) -> Self { Self { _card: C::default(), id } }
pub fn id(&self) -> api::SysId { self.id }
}
impl<C: SystemCard> Clone for SystemHandle<C> {
fn clone(&self) -> Self { Self::new(self.id) }
}
/// Type-erased system handle, not used for anything at the moment
pub trait DynSystemHandle {
fn id(&self) -> api::SysId;
fn get_card(&self) -> &dyn DynSystemCard;
fn get_card(&self) -> Box<dyn DynSystemCard>;
}
impl<C: SystemCard> DynSystemHandle for SystemHandle<C> {
fn id(&self) -> api::SysId { self.id }
fn get_card(&self) -> &dyn DynSystemCard { &self.card }
fn get_card(&self) -> Box<dyn DynSystemCard> { Box::new(C::default()) }
}
/// 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
/// one of its direct dependencies.
pub async fn sys_req<Sys: SystemCard, Req: Request + Into<Sys::Req>>(req: Req) -> Req::Response {
let mut msg = Vec::new();
req.into().encode_vec(&mut msg);
let cted = dyn_cted();
let own_inst = cted.inst();
let owner = if own_inst.card().type_id() == TypeId::of::<Sys>() {
sys_id()
} else {
(cted.deps().find(|s| s.get_card().type_id() == TypeId::of::<Sys>()))
.expect("System not in dependency array yet we have a handle somehow")
.id()
};
let reply = request(api::SysFwd(owner, msg)).await;
Req::Response::decode(std::pin::pin!(&reply[..])).await.unwrap()
}

View File

@@ -14,18 +14,23 @@ use orchid_base::{
};
use task_local::task_local;
use crate::api;
use crate::conv::ToExpr;
use crate::entrypoint::request;
use crate::expr::Expr;
use crate::gen_expr::GExpr;
use crate::system::sys_id;
use crate::tree::{GenTok, GenTokTree};
use crate::{Expr, ToExpr, api, request, sys_id};
/// [PTokTree] without [orchid_base::Pos] metadata
pub type PTok = Token<Expr, Never>;
/// [TokTree] received by [Parser], or returned from recursive [crate::Lexer]
/// call. It is different from [GenTok] because it cannot be [Token::NewExpr].
/// Convert with [p_tok2gen], [p_tree2gen], [p_v2gen]
pub type PTokTree = TokTree<Expr, Never>;
/// Wrapper [Snippet] around a slice of [PTokTree] with a fallback item for
/// [orchid_base::Pos] metadata
pub type PSnippet<'a> = Snippet<'a, Expr, Never>;
/// Convert ingress syntax tokens into egress for interpolation into the output
///
/// See also [p_tree2gen], [p_v2gen]
pub fn p_tok2gen(tok: PTok) -> GenTok {
match_mapping!(tok, PTok => GenTok {
Comment(s), Name(n), BR, Handle(ex), Bottom(err),
@@ -36,15 +41,33 @@ pub fn p_tok2gen(tok: PTok) -> GenTok {
PTok::NewExpr(never) => match never {}
})
}
/// Convert ingress syntax tokens into egress for interpolation into the output
///
/// See also [p_tok2gen], [p_v2gen]
pub fn p_tree2gen(tree: PTokTree) -> GenTokTree {
TokTree { tok: p_tok2gen(tree.tok), sr: tree.sr }
}
/// Convert ingress syntax tokens into egress for interpolation into the output
///
/// See also [p_tok2gen], [p_tree2gen]
pub fn p_v2gen(v: impl IntoIterator<Item = PTokTree>) -> Vec<GenTokTree> {
v.into_iter().map(p_tree2gen).collect_vec()
}
/// A parser extension for the stage of parsing that converts source lines into
/// a module tree. These are invoked by usercode intentionally through a custom
/// keyword at the start of the line
///
/// Unlike [crate::Lexer]'s [crate::err_not_applicable], these don't have a
/// mechanism to reject work. Each keyword must refer to one and exactly one
/// parser
pub trait Parser: Send + Sync + Sized + Default + 'static {
/// The keyword at the head of the line that invokes this parser
const LINE_HEAD: &'static str;
/// Parsing work. If the line head is preceded by the keyword "export", the
/// 2nd parameter is true. Both the line head and the optional export keyword
/// are trimmed off the 4th parameter. The parser should forward all comments
/// to the generated lines
fn parse<'a>(
ctx: ParsCtx<'a>,
exported: bool,
@@ -53,8 +76,11 @@ pub trait Parser: Send + Sync + Sized + Default + 'static {
) -> impl Future<Output = OrcRes<Vec<ParsedLine>>> + 'a;
}
/// Type-erased [Parser]
pub trait DynParser: Send + Sync + 'static {
/// Type-erased [Parser::LINE_HEAD]
fn line_head(&self) -> &'static str;
/// Type-erased [Parser::parse]
fn parse<'a>(
&self,
ctx: ParsCtx<'a>,
@@ -77,14 +103,18 @@ impl<T: Parser> DynParser for T {
}
}
/// Trait-object of [Parser]
pub type ParserObj = &'static dyn DynParser;
/// Information about the parsing context
pub struct ParsCtx<'a> {
_parse: PhantomData<&'a mut ()>,
module: Sym,
}
impl<'a> ParsCtx<'a> {
pub(crate) fn new(module: Sym) -> Self { Self { _parse: PhantomData, module } }
/// The full path of the module that contains the line the parser was invoked
/// with
pub fn module(&self) -> Sym { self.module.clone() }
}
@@ -100,12 +130,19 @@ pub(crate) fn with_parsed_const_ctx<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBox
Box::pin(CONST_TBL.scope(RefCell::default(), NEXT_CONST_ID.scope(id_cell, fut)))
}
/// A logical subtree returned from a parser
pub struct ParsedLine {
/// Source location associated with this item
pub sr: SrcRange,
/// Comments placed above this line
pub comments: Vec<Comment>,
/// Possible nodes in the logical tree
pub kind: ParsedLineKind,
}
impl ParsedLine {
/// Define a constant. The callback is only called later if the constant is
/// referenced, and it can call [crate::refl] to base its value on the module
/// tree or use its argument for context-specific queries
pub fn cnst<'a, R: ToExpr + 'static, F: AsyncFnOnce(ConstCtx) -> R + 'static>(
sr: &SrcRange,
comments: impl IntoIterator<Item = &'a Comment>,
@@ -118,6 +155,12 @@ impl ParsedLine {
let comments = comments.into_iter().cloned().collect();
ParsedLine { comments, sr: sr.clone(), kind }
}
/// Define a module containing additional items. `use_prelude` determines
/// whether the globally visible imports specified in the preludes of
/// extensions will be added to this module. In practice, it should typically
/// be enabled if you are interpolating any user-expressions or user-lines
/// into the module and disabled if your custom line parses all input and does
/// not interact with other plugins and macros
pub fn module<'a>(
sr: &SrcRange,
comments: impl IntoIterator<Item = &'a Comment>,
@@ -131,7 +174,7 @@ impl ParsedLine {
let comments = comments.into_iter().cloned().collect();
ParsedLine { comments, sr: sr.clone(), kind: line_kind }
}
pub async fn into_api(self) -> api::ParsedLine {
pub(crate) async fn into_api(self) -> api::ParsedLine {
api::ParsedLine {
comments: self.comments.into_iter().map(|c| c.to_api()).collect(),
source_range: self.sr.to_api(),
@@ -162,27 +205,50 @@ pub(crate) async fn linev_into_api(v: Vec<ParsedLine>) -> Vec<api::ParsedLine> {
join_all(v.into_iter().map(|l| l.into_api())).await
}
/// Lines in the logical tree returnable from parsers
pub enum ParsedLineKind {
/// A single subtree in the logical tree. This kind can more conveniently be
/// generated with [ParsedLine::cnst] or [ParsedLine::module]
Mem(ParsedMem),
/// Additional syntax that will be passed to another line parser and may
/// evaluate to zero or more lines
Rec(Vec<GenTokTree>),
}
/// A single subtree in the logical tree, also visible via [crate::refl]
pub struct ParsedMem {
/// Name must be unique
pub name: IStr,
/// Whether the subtree is visible outside its enclosing module. If not, only
/// siblings and their descendants will be able to import it
pub exported: bool,
/// Variants
pub kind: ParsedMemKind,
}
/// Kind of [ParsedMem]
pub enum ParsedMemKind {
/// A constant
Const(BoxConstCallback),
Mod { lines: Vec<ParsedLine>, use_prelude: bool },
/// A module with additional descendants
Mod {
/// Logical lines inside the module
lines: Vec<ParsedLine>,
/// Whether the module will include prelude imports provided by all
/// extensions
use_prelude: bool,
},
}
/// Enable a generated constant to query about its environment
#[derive(Clone)]
pub struct ConstCtx {
constid: api::ParsedConstId,
}
impl ConstCtx {
/// Resolve a set of local names into the full names they would point to if
/// they were globally bound. Errors produced at this stage are soft, as the
/// names may still be found to be locally bound within the expression
pub fn names<'b>(
&'b self,
names: impl IntoIterator<Item = &'b Sym> + 'b,
@@ -202,6 +268,7 @@ impl ConstCtx {
}
})
}
/// Static-length version of [Self::names]
pub async fn names_n<const N: usize>(&self, names: [&Sym; N]) -> [OrcRes<Sym>; N] {
self.names(names).collect::<Vec<_>>().await.try_into().expect("Lengths must match")
}

View File

@@ -9,39 +9,45 @@ use memo_map::MemoMap;
use orchid_base::{IStr, NameLike, VPath, es, iv};
use task_local::task_local;
use crate::api;
use crate::entrypoint::request;
use crate::system::sys_id;
use crate::{api, request, sys_id};
#[derive(Debug)]
pub struct ReflMemData {
// None for inferred steps
struct ReflMemData {
/// None for inferred steps
public: OnceCell<bool>,
kind: ReflMemKind,
}
/// Potentially partial reflected information about a member inside a module
#[derive(Clone, Debug)]
pub struct ReflMem(Rc<ReflMemData>);
impl ReflMem {
pub fn kind(&self) -> ReflMemKind { self.0.kind.clone() }
}
/// The kind of [ReflMem]
#[derive(Clone, Debug)]
pub enum ReflMemKind {
/// A plain constant. Information about constants can be obtained by passing a
/// [crate::gen_expr::GExprKind::Const] to the interpreter
Const,
/// A module that can be reflected further
Mod(ReflMod),
}
#[derive(Debug)]
pub struct ReflModData {
struct ReflModData {
inferred: Mutex<bool>,
path: VPath,
members: MemoMap<IStr, ReflMem>,
}
/// A module whose members can be listed and inspected
#[derive(Clone, Debug)]
pub struct ReflMod(Rc<ReflModData>);
impl ReflMod {
/// Retrieve the path of the module
pub fn path(&self) -> &[IStr] { &self.0.path[..] }
/// Whether this path is the root or if it has a parent
pub fn is_root(&self) -> bool { self.0.path.is_empty() }
async fn try_populate(&self) -> Result<(), api::LsModuleError> {
let path_tok = iv(&self.0.path[..]).await;
@@ -70,6 +76,7 @@ impl ReflMod {
}
Ok(())
}
/// Find a child by name within the module
pub async fn get_child(&self, key: &IStr) -> Option<ReflMem> {
let inferred_g = self.0.inferred.lock().await;
if let Some(mem) = self.0.members.get(key) {
@@ -90,6 +97,9 @@ impl ReflMod {
}
self.0.members.get(key).cloned()
}
/// Find a descendant by sub-path within the module. Note that this is not the
/// same as the paths accepted by import statements, as the `self` and `super`
/// keywords don't work
pub async fn get_by_path(&self, path: &[IStr]) -> Result<ReflMem, InvalidPathError> {
let (next, tail) = path.split_first().expect("Attempted to walk by empty path");
let inferred_g = self.0.inferred.lock().await;
@@ -138,8 +148,11 @@ task_local! {
static REFL_ROOTS: RefCell<HashMap<api::SysId, ReflMod>>
}
/// Indicates that the caller provided a path that does not exist
#[derive(Clone, Debug)]
pub struct InvalidPathError {
/// When unwinding a recursive path lookup, decides whether the temporary
/// member created for the parent object should be kept or deleted
keep_ancestry: bool,
}
@@ -154,12 +167,18 @@ fn default_member(is_root: bool, kind: ReflMemKind) -> ReflMem {
}))
}
/// Obtains the root module for reflection. This function may only be called in
/// callbacks provided by [crate::OwnedAtom] except for
/// [crate::OwnedAtom::free], callbacks provided by [crate::ThinAtom], the
/// callback for [crate::ParsedLine::cnst], [crate::gen_expr] callbacks, and
/// other places where we know that the interpreter is running and holding a
/// reference to the module tree
pub fn refl() -> ReflMod {
REFL_ROOTS.with(|tbl| {
tbl.borrow_mut().entry(sys_id()).or_insert_with(|| default_module(VPath::new([]))).clone()
})
}
pub fn with_refl_roots<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
pub(crate) fn with_refl_roots<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(REFL_ROOTS.scope(RefCell::default(), fut))
}

View File

@@ -1,26 +1,52 @@
use std::num::NonZero;
use std::time::Duration;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use crate::api;
use crate::atom::AtomMethod;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
pub struct Spawn(pub api::ExprTicket);
impl Request for Spawn {
type Response = api::ExprTicket;
}
/// Execute the atom as a command.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
pub struct RunCommand;
impl Request for RunCommand {
type Response = Option<api::Expression>;
}
impl AtomMethod for RunCommand {
const NAME: &str = "orchid::cmd::run";
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
pub struct AsDuration;
impl Request for AsDuration {
type Response = Duration;
}
impl AtomMethod for AsDuration {
const NAME: &str = "orchid::time::as_duration";
}
/// Represents [std::io::ErrorKind] values that are produced while operating on
/// already-opened files
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)]
pub enum IoErrorKind {
BrokenPipe,
UnexpectedEof,
ConnectionAborted,
/// Produced when a stream ends prematurely due to an identifiably unintended
/// reason, such as a TCP socket timeout, the file becoming inaccessible or
/// disappearing, or
Interrupted,
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",
IoErrorKind::Interrupted => "Stream interrupted",
}
}
}

View File

@@ -1,78 +1,21 @@
use std::any::{Any, TypeId, type_name};
use std::fmt::Debug;
use std::future::Future;
use std::num::NonZero;
use std::rc::Rc;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use orchid_api_traits::{Coding, Decode, Encode, Request};
use orchid_base::{BoxedIter, Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym};
use orchid_base::{Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym};
use task_local::task_local;
use crate::api;
use crate::atom::{AtomOps, AtomTypeId, Atomic, AtomicFeatures};
use crate::coroutine_exec::Replier;
use crate::entrypoint::request;
use crate::func_atom::{Fun, Lambda};
use crate::lexer::LexerObj;
use crate::parser::ParserObj;
use crate::system_ctor::{CtedObj, SystemCtor};
use crate::tree::GenMember;
use crate::{Cted, CtedObj, DynSystemCard, LexerObj, ParserObj, SystemCard, SystemCtor, api};
/// 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 AtomOps>>>;
}
/// 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 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 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 AtomOps>>> {
Box::new(Self::atoms().into_iter())
}
}
pub type CardForSystem<T> = <<T as System>::Ctor as SystemCtor>::Card;
pub type ReqForSystem<T> = <CardForSystem<T> as SystemCard>::Req;
/// System as defined by author
pub trait System: SystemCard + 'static {
pub trait System: Debug + 'static {
type Ctor: SystemCtor<Instance = Self>;
fn prelude(&self) -> impl Future<Output = Vec<Sym>>;
fn env(&self) -> impl Future<Output = Vec<GenMember>>;
fn lexers(&self) -> Vec<LexerObj>;
@@ -80,11 +23,11 @@ pub trait System: SystemCard + 'static {
fn request<'a>(
&self,
hand: Box<dyn ReqHandle<'a> + 'a>,
req: Self::Req,
req: ReqForSystem<Self>,
) -> impl Future<Output = Receipt<'a>>;
}
pub trait DynSystem: DynSystemCard + 'static {
pub trait DynSystem: Debug + 'static {
fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec<Sym>>;
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>>;
fn dyn_lexers(&self) -> Vec<LexerObj>;
@@ -93,7 +36,7 @@ pub trait DynSystem: DynSystemCard + 'static {
&'a self,
hand: Box<dyn ReqReader<'b> + 'b>,
) -> LocalBoxFuture<'a, Receipt<'b>>;
fn card(&self) -> &dyn DynSystemCard;
fn card(&self) -> Box<dyn DynSystemCard>;
}
impl<T: System> DynSystem for T {
@@ -106,11 +49,11 @@ impl<T: System> DynSystem for T {
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();
let value = hand.read_req().await.unwrap();
self.request(hand.finish().await, value).await
})
}
fn card(&self) -> &dyn DynSystemCard { self }
fn card(&self) -> Box<dyn DynSystemCard> { Box::new(CardForSystem::<Self>::default()) }
}
#[derive(Clone)]
@@ -125,23 +68,7 @@ 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()) }
/// 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
/// one of its direct dependencies.
pub async fn sys_req<Sys: SystemCard, Req: Request + Into<Sys::Req>>(req: Req) -> Req::Response {
let mut msg = Vec::new();
req.into().encode_vec(&mut msg);
let cted = cted();
let own_inst = cted.inst();
let owner = if own_inst.card().type_id() == TypeId::of::<Sys>() {
sys_id()
} else {
(cted.deps().find(|s| s.get_card().type_id() == TypeId::of::<Sys>()))
.expect("System not in dependency array")
.id()
};
let reply = request(api::SysFwd(owner, msg)).await;
Req::Response::decode(std::pin::pin!(&reply[..])).await.unwrap()
pub fn dyn_cted() -> CtedObj { SYS_CTX.with(|cx| cx.1.clone()) }
pub fn cted<C: SystemCtor>() -> Cted<C> {
Rc::downcast::<Cted<C>>(dyn_cted().as_any()).unwrap().as_ref().clone()
}

View File

@@ -0,0 +1,60 @@
use std::any::{Any, TypeId, type_name};
use std::fmt::Debug;
use std::num::NonZero;
use orchid_api_traits::Coding;
use orchid_base::BoxedIter;
use crate::{AtomOps, AtomTypeId, Atomic, AtomicFeatures, Fun, Lambda, Replier, SystemCtor};
/// Description of a system. This is intended to be a ZST storing the static
/// properties of a [SystemCtor] which should be known to foreign systems
pub trait SystemCard: Debug + Default + 'static {
type Ctor: SystemCtor;
type Req: Coding;
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomOps>>>;
}
/// 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 AtomOps>>>;
}
impl<T: SystemCard> DynSystemCard for T {
fn name(&self) -> &'static str { T::Ctor::NAME }
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomOps>>> {
Box::new(Self::atoms().into_iter())
}
}
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)
pub(crate) fn general_atoms() -> impl Iterator<Item = Option<Box<dyn AtomOps>>> {
[Some(Fun::ops()), Some(Lambda::ops()), Some(Replier::ops())].into_iter()
}

View File

@@ -1,33 +1,31 @@
use std::any::Any;
use std::fmt::Debug;
use std::sync::Arc;
use std::rc::Rc;
use orchid_base::{BoxedIter, box_empty, box_once};
use ordered_float::NotNan;
use crate::api;
use crate::other_system::{DynSystemHandle, SystemHandle};
use crate::system::{DynSystem, System, SystemCard};
use crate::{DynSystem, DynSystemHandle, System, SystemCard, SystemHandle, api};
#[derive(Debug)]
pub struct Cted<Ctor: SystemCtor + ?Sized> {
pub deps: <Ctor::Deps as DepDef>::Sat,
pub inst: Arc<Ctor::Instance>,
pub inst: Rc<Ctor::Instance>,
}
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 + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any(self: Rc<Self>) -> Rc<dyn Any>;
fn deps<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>;
fn inst(&self) -> Arc<dyn DynSystem>;
fn inst(&self) -> Rc<dyn DynSystem>;
}
impl<C: SystemCtor + ?Sized> DynCted for Cted<C> {
fn as_any(&self) -> &dyn Any { self }
fn as_any(self: Rc<Self>) -> Rc<dyn Any> { self }
fn deps<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)> { self.deps.iter() }
fn inst(&self) -> Arc<dyn DynSystem> { self.inst.clone() }
fn inst(&self) -> Rc<dyn DynSystem> { self.inst.clone() }
}
pub type CtedObj = Arc<dyn DynCted>;
pub type CtedObj = Rc<dyn DynCted>;
pub trait DepSat: Debug + Clone + 'static {
fn iter<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>;
@@ -61,7 +59,8 @@ impl DepDef for () {
pub trait SystemCtor: Debug + 'static {
type Deps: DepDef;
type Instance: System;
type Instance: System<Ctor = Self>;
type Card: SystemCard<Ctor = Self>;
const NAME: &'static str;
const VERSION: f64;
/// Create a system instance.
@@ -85,8 +84,8 @@ impl<T: SystemCtor> DynSystemCtor for T {
fn new_system(&self, api::NewSystem { system: _, id: _, depends }: &api::NewSystem) -> CtedObj {
let mut ids = depends.iter().copied();
let deps = T::Deps::create(&mut || ids.next().unwrap());
let inst = Arc::new(self.inst(deps.clone()));
Arc::new(Cted::<T> { deps, inst })
let inst = Rc::new(self.inst(deps.clone()));
Rc::new(Cted::<T> { deps, inst })
}
}

View File

@@ -1,7 +1,6 @@
use std::rc::Rc;
use crate::entrypoint::ExtensionBuilder;
use crate::ext_port::ExtPort;
use crate::{ExtPort, ExtensionBuilder};
/// Run an extension inside a Tokio localset. Since the extension API does not
/// provide a forking mechanism, it can safely abort once the localset is
/// exhausted. If an extension absolutely needs a parallel thread, it can import
@@ -10,7 +9,7 @@ use crate::ext_port::ExtPort;
/// value returned by [crate::system_ctor::SystemCtor::inst] to initiate
/// shutdown.
#[cfg(feature = "tokio")]
pub async fn tokio_entrypoint(builder: ExtensionBuilder) {
pub async fn __tokio_entrypoint(builder: ExtensionBuilder) {
use std::cell::RefCell;
use async_event::Event;
@@ -61,6 +60,6 @@ pub async fn tokio_entrypoint(builder: ExtensionBuilder) {
macro_rules! tokio_main {
($builder:expr) => {
#[tokio::main]
pub async fn main() { $crate::tokio::tokio_entrypoint($builder).await }
pub async fn main() { $crate::__tokio_entrypoint($builder).await }
};
}

View File

@@ -13,13 +13,16 @@ use substack::Substack;
use task_local::task_local;
use trait_set::trait_set;
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};
use crate::{BorrowedExprStore, Expr, ExprFunc, ExprHandle, Fun, ToExpr, api};
/// Tokens generated by lexers and parsers
///
/// See: [GenTok], [Token], [crate::Lexer], [crate::Parser]
pub type GenTokTree = TokTree<Expr, GExpr>;
/// Tokens generated by lexers and parsers - without location data
///
/// See: [GenTokTree], [Token], [crate::Lexer], [crate::Parser]
pub type GenTok = Token<Expr, GExpr>;
impl TokenVariant<api::Expression> for GExpr {
@@ -41,9 +44,15 @@ impl TokenVariant<api::ExprTicket> for Expr {
async fn into_api(self, (): &mut Self::ToApiCtx<'_>) -> api::ExprTicket { self.handle().ticket() }
}
/// Embed a literal value in generated syntax.
pub async fn x_tok(x: impl ToExpr) -> GenTok { GenTok::NewExpr(x.to_gen().await) }
/// Embed a reference to a constant in generated syntax. The constant doesn't
/// need to be visible per export rules, and if it doesn't exist, the expression
/// will raise a runtime error
pub async fn ref_tok(path: Sym) -> GenTok { GenTok::NewExpr(path.to_gen().await) }
/// Create a new subtree that is evaluated as-needed, asynchronously, and can
/// use its own path to determine its value
pub fn lazy(
public: bool,
name: &str,
@@ -51,30 +60,32 @@ pub fn lazy(
) -> Vec<GenMember> {
vec![GenMember {
name: name.to_string(),
kind: MemKind::Lazy(LazyMemberFactory::new(cb)),
kind: LazyMemKind::Lazy(LazyMemberFactory::new(cb)),
comments: vec![],
public,
}]
}
/// A constant node in the module tree
pub fn cnst(public: bool, name: &str, value: impl ToExpr + Clone + 'static) -> Vec<GenMember> {
lazy(public, name, async |_| MemKind::Const(value.to_gen().await))
}
/// A module in the tree. These can be merged by [merge_trivial]
pub fn module(
public: bool,
name: &str,
mems: impl IntoIterator<Item = Vec<GenMember>>,
) -> Vec<GenMember> {
let (name, kind) = root_mod(name, mems);
let (name, kind) = (name.to_string(), LazyMemKind::Mod(mems.into_iter().flatten().collect()));
vec![GenMember { name, kind, public, comments: vec![] }]
}
pub fn root_mod(name: &str, mems: impl IntoIterator<Item = Vec<GenMember>>) -> (String, MemKind) {
(name.to_string(), MemKind::module(mems))
}
/// A Rust function that is passed to Orchid via [Fun]
pub fn fun<I, O>(public: bool, name: &str, xf: impl ExprFunc<I, O>) -> Vec<GenMember> {
let fac =
LazyMemberFactory::new(async move |sym| MemKind::Const(new_atom(Fun::new(sym, xf).await)));
vec![GenMember { name: name.to_string(), kind: MemKind::Lazy(fac), public, comments: vec![] }]
vec![GenMember { name: name.to_string(), kind: LazyMemKind::Lazy(fac), public, comments: vec![] }]
}
/// A chain of nested modules with names taken from the duble::colon::delimited
/// path ultimately containing the elements
pub fn prefix(path: &str, items: impl IntoIterator<Item = Vec<GenMember>>) -> Vec<GenMember> {
let mut items = items.into_iter().flatten().collect_vec();
for step in path.split("::").collect_vec().into_iter().rev() {
@@ -82,7 +93,7 @@ pub fn prefix(path: &str, items: impl IntoIterator<Item = Vec<GenMember>>) -> Ve
}
items
}
/// Add comments to a set of members
pub fn comments<'a>(
cmts: impl IntoIterator<Item = &'a str>,
mut val: Vec<GenMember>,
@@ -101,20 +112,20 @@ pub fn comments<'a>(
/// - Duplicate constants result in an error
/// - A combination of lazy and anything results in an error
pub fn merge_trivial(trees: impl IntoIterator<Item = Vec<GenMember>>) -> Vec<GenMember> {
let mut all_members = HashMap::<String, (MemKind, Vec<String>)>::new();
let mut all_members = HashMap::<String, (LazyMemKind, Vec<String>)>::new();
for mem in trees.into_iter().flatten() {
assert!(mem.public, "Non-trivial merge in {}", mem.name);
match mem.kind {
unit @ (MemKind::Const(_) | MemKind::Lazy(_)) => {
unit @ (LazyMemKind::Const(_) | LazyMemKind::Lazy(_)) => {
let prev = all_members.insert(mem.name.clone(), (unit, mem.comments.into_iter().collect()));
assert!(prev.is_none(), "Conflict in trivial tree merge on {}", mem.name);
},
MemKind::Mod(members) => match all_members.entry(mem.name.clone()) {
LazyMemKind::Mod(members) => match all_members.entry(mem.name.clone()) {
hashbrown::hash_map::Entry::Vacant(slot) => {
slot.insert((MemKind::Mod(members), mem.comments.into_iter().collect()));
slot.insert((LazyMemKind::Mod(members), mem.comments.into_iter().collect()));
},
hashbrown::hash_map::Entry::Occupied(mut old) => match old.get_mut() {
(MemKind::Mod(old_items), old_cmts) => {
(LazyMemKind::Mod(old_items), old_cmts) => {
let mut swap = vec![];
std::mem::swap(&mut swap, old_items);
*old_items = merge_trivial([swap, members]);
@@ -135,7 +146,7 @@ trait_set! {
trait LazyMemberCallback =
FnOnce(Sym) -> LocalBoxFuture<'static, MemKind> + DynClone
}
pub struct LazyMemberFactory(Box<dyn LazyMemberCallback>);
pub(crate) struct LazyMemberFactory(Box<dyn LazyMemberCallback>);
impl LazyMemberFactory {
pub fn new(cb: impl AsyncFnOnce(Sym) -> MemKind + Clone + 'static) -> Self {
Self(Box::new(|s| cb(s).boxed_local()))
@@ -147,10 +158,10 @@ impl Clone for LazyMemberFactory {
}
pub struct GenMember {
pub name: String,
pub kind: MemKind,
pub public: bool,
pub comments: Vec<String>,
pub(crate) name: String,
pub(crate) kind: LazyMemKind,
pub(crate) public: bool,
pub(crate) comments: Vec<String>,
}
impl GenMember {
pub(crate) async fn into_api(self, tia_cx: &mut impl TreeIntoApiCtx) -> api::Member {
@@ -161,16 +172,24 @@ impl GenMember {
}
}
/// Items in the tree after deferrals have been resolved
pub enum MemKind {
Const(GExpr),
Mod(Vec<GenMember>),
Lazy(LazyMemberFactory),
}
impl MemKind {
pub async fn cnst(val: impl ToExpr) -> Self { Self::Const(val.to_gen().await) }
pub fn module(mems: impl IntoIterator<Item = Vec<GenMember>>) -> Self {
Self::Mod(mems.into_iter().flatten().collect())
}
}
pub(crate) enum LazyMemKind {
Const(GExpr),
Mod(Vec<GenMember>),
Lazy(LazyMemberFactory),
}
impl LazyMemKind {
pub(crate) async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MemberKind {
match self {
Self::Lazy(lazy) => api::MemberKind::Lazy(add_lazy(ctx, lazy)),
@@ -189,7 +208,7 @@ impl MemKind {
}
}
pub enum MemberRecord {
pub(crate) enum MemberRecord {
Gen(Vec<IStr>, LazyMemberFactory),
Res,
}
@@ -201,7 +220,7 @@ task_local! {
static LAZY_MEMBERS: LazyMemberStore;
}
pub fn with_lazy_member_store<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
pub(crate) fn with_lazy_member_store<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(LAZY_MEMBERS.scope(LazyMemberStore::default(), fut))
}
@@ -215,7 +234,7 @@ fn add_lazy(cx: &impl TreeIntoApiCtx, fac: LazyMemberFactory) -> api::TreeId {
})
}
pub async fn get_lazy(id: api::TreeId) -> (Sym, MemKind) {
pub(crate) async fn get_lazy(id: api::TreeId) -> (Sym, LazyMemKind) {
let (path, cb) =
LAZY_MEMBERS.with(|tbl| match tbl.0.borrow_mut().insert(id, MemberRecord::Res) {
None => panic!("Tree for ID not found"),
@@ -223,7 +242,10 @@ pub async fn get_lazy(id: api::TreeId) -> (Sym, MemKind) {
Some(MemberRecord::Gen(path, cb)) => (path, cb),
});
let path = Sym::new(path).await.unwrap();
(path.clone(), cb.build(path).await)
(path.clone(), match cb.build(path).await {
MemKind::Const(c) => LazyMemKind::Const(c),
MemKind::Mod(m) => LazyMemKind::Mod(m),
})
}
pub(crate) trait TreeIntoApiCtx {
@@ -231,7 +253,7 @@ pub(crate) trait TreeIntoApiCtx {
fn path(&self) -> impl Iterator<Item = IStr>;
}
pub struct TreeIntoApiCtxImpl<'a> {
pub(crate) struct TreeIntoApiCtxImpl<'a> {
pub basepath: &'a [IStr],
pub path: Substack<'a, IStr>,
}