forked from Orchid/orchid
Compiles again after command subsystem
terrified to start testing
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
60
orchid-extension/src/cmd_atom.rs
Normal file
60
orchid-extension/src/cmd_atom.rs
Normal 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),
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
60
orchid-extension/src/system_card.rs
Normal file
60
orchid-extension/src/system_card.rs
Normal 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()
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user