diff --git a/Cargo.lock b/Cargo.lock index 75fb0b5..685868f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1139,6 +1139,7 @@ dependencies = [ "ordered-float", "pastey", "substack", + "task-local", "tokio", "tokio-util", "trait-set", diff --git a/async-fn-stream/src/lib.rs b/async-fn-stream/src/lib.rs index 1b5ff22..808c70e 100644 --- a/async-fn-stream/src/lib.rs +++ b/async-fn-stream/src/lib.rs @@ -23,7 +23,7 @@ pub fn stream<'a, T: 'a>( let (send, recv) = mpsc::channel::(1); let fut = async { f(StreamCtx(send, PhantomData)).await }; // use options to ensure that the stream is driven to exhaustion - select_with_strategy(fut.into_stream().map(|()| None), recv.map(|t| Some(t)), left_strat) + select_with_strategy(fut.into_stream().map(|()| None), recv.map(Some), left_strat) .filter_map(async |opt| opt) } diff --git a/notes/commands.md b/notes/commands.md index 404da3f..3470e34 100644 --- a/notes/commands.md +++ b/notes/commands.md @@ -18,4 +18,4 @@ The orchid embedder and extension API mean something different by command than a ## Continuation -Since commands are expected to be composed into arbitrarily deep TC structures,to avoid a memory leak, commands should not remain passively present in the system; they must be able to express certain outcomes as plain data and return. The most obvious of such outcomes is the single continuation wherein a subexpression evaluating to another command from the same set will eventually run, but other ones may potentially exist. Are there any that cannot be emulated by continuing with a call to an environment constant which expresses the outcome in terms of its parameters? +Since commands are expected to be composed into arbitrarily deep TC structures,to avoid a memory leak, commands should not remain passively present in the system; they must be able to express certain outcomes as plain data and return. The most obvious of such outcomes is the single continuation wherein a subexpression evaluating to another command from the same set will eventually run, and it can emulate more complex patterns by continuing with a call to an environment constant which expresses the more complex outcome in terms of its parameters diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs index 5c33c1b..260ba2f 100644 --- a/orchid-api/src/atom.rs +++ b/orchid-api/src/atom.rs @@ -5,9 +5,7 @@ use itertools::Itertools; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use crate::{ - ExprTicket, Expression, ExtHostReq, FormattingUnit, HostExtReq, OrcResult, SysId, TStrv, -}; +use crate::{ExprTicket, Expression, ExtHostReq, FormattingUnit, HostExtReq, SysId, TStrv}; #[derive(Clone, Coding)] pub struct AtomData(pub Vec); @@ -97,8 +95,8 @@ impl Request for DeserAtom { /// A request blindly routed to the system that provides an atom. #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(AtomReq, HostExtReq)] -pub struct Fwded(pub Atom, pub TStrv, pub Vec); -impl Request for Fwded { +pub struct FinalFwded(pub Atom, pub TStrv, pub Vec); +impl Request for FinalFwded { type Response = Option>; } @@ -109,32 +107,6 @@ impl Request for Fwd { type Response = Option>; } -/// What to do after a command has finished executing -#[derive(Clone, Debug, Coding)] -pub enum NextStep { - /// Add more work. Parallel work is fairly executed in parallel, so different - /// command chains can block on each other. When the command queue is empty, - /// the interpreter may exit without waiting for timers. - Continue { - /// Run these commands immediately. Since timers don't keep the interpreter - /// alive, this should usually be non-empty, but this is not required. - immediate: Vec, - /// Schedule these commands after the specified number of milliseconds, if - /// the interpreter had not exited by then. - delayed: Vec<(NonZeroU64, Expression)>, - }, - /// Discard the rest of the queue and exit. It is possible to fail the program - /// without raising an error because the convention on most OSes is to - /// separate error reporting from a failure exit. - Exit { success: bool }, -} -#[derive(Clone, Debug, Coding, Hierarchy)] -#[extends(AtomReq, HostExtReq)] -pub struct Command(pub Atom); -impl Request for Command { - type Response = OrcResult; -} - /// Notification that an atom is being dropped because its associated expression /// isn't referenced anywhere. This should have no effect if the atom's `drop` /// flag is false. @@ -166,8 +138,8 @@ impl Request for ExtAtomPrint { pub enum AtomReq { CallRef(CallRef), FinalCall(FinalCall), - Fwded(Fwded), - Command(Command), + FwdedRef(FinalFwded), + FinalFwded(FinalFwded), AtomPrint(AtomPrint), SerializeAtom(SerializeAtom), } @@ -177,9 +149,9 @@ impl AtomReq { pub fn get_atom(&self) -> &Atom { match self { Self::CallRef(CallRef(a, ..)) - | Self::Command(Command(a)) | Self::FinalCall(FinalCall(a, ..)) - | Self::Fwded(Fwded(a, ..)) + | Self::FwdedRef(FinalFwded(a, ..)) + | Self::FinalFwded(FinalFwded(a, ..)) | Self::AtomPrint(AtomPrint(a)) | Self::SerializeAtom(SerializeAtom(a)) => a, } diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index 2fb5100..250f183 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -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::(); @@ -189,6 +187,10 @@ pub trait AtomMethod: Coding + InHierarchy { const NAME: &str; } +task_local! { + pub(crate) static ATOM_WITHOUT_HANDLE_FINAL_IMPL: Rc>>>; +} + /// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be /// registered in [Atomic::reg_methods] pub trait Supports: Atomic { @@ -197,25 +199,53 @@ pub trait Supports: Atomic { hand: Box + '_>, req: M, ) -> impl Future>>; + fn handle_final<'a>( + self, + hand: Box + '_>, + req: M, + ) -> impl Future>> { + 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 { fn handle<'a, 'b: 'a>( &'a self, atom: &'a A, - req: Box + 'a>, + reader: Box + 'a>, + ) -> LocalBoxFuture<'a, ()>; + fn handle_final<'a, 'b: 'a>( + &'a self, + atom: A, + reader: Box + 'a>, ) -> LocalBoxFuture<'a, ()>; } struct AtomMethodHandler(PhantomData, PhantomData); impl> HandleAtomMethod for AtomMethodHandler { fn handle<'a, 'b: 'a>( &'a self, - a: &'a A, + atom: &'a A, mut reader: Box + 'a>, ) -> LocalBoxFuture<'a, ()> { Box::pin(async { let req = reader.read_req::().await.unwrap(); - let _ = Supports::::handle(a, reader.finish().await, req).await.unwrap(); + let _ = Supports::::handle(atom, reader.finish().await, req).await.unwrap(); + }) + } + fn handle_final<'a, 'b: 'a>( + &'a self, + atom: A, + mut reader: Box + 'a>, + ) -> LocalBoxFuture<'a, ()> { + Box::pin(async { + let req = reader.read_req::().await.unwrap(); + let _ = Supports::::handle_final(atom, reader.finish().await, req).await.unwrap(); }) } } @@ -252,6 +282,20 @@ pub(crate) struct MethodSet { handlers: HashMap>>, } impl MethodSet { + pub(crate) async fn final_dispatch<'a>( + &self, + atom: A, + key: Sym, + req: Box + '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 Format for TAtom { pub(crate) struct AtomCtx<'a>(pub &'a [u8], pub Option); -pub enum Next { - ExitSuccess, - Continue(Continuation), -} - -#[derive(Default)] -pub struct Continuation { - immediate: Vec>, - delayed: Vec<(NonZeroU64, LocalBoxStream<'static, GExpr>)>, -} -impl Continuation { - pub fn immediate(mut self, expr: T) -> Self { - self.immediate.push(Box::pin(expr.into_gexpr_stream())); - self - } - pub fn schedule(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 for Next { - fn from(value: Continuation) -> Self { Self::Continue(value) } -} - -impl From 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 for CmdError { - fn from(FatalError: FatalError) -> Self { Self::FatalError } -} -impl From for CmdError { - fn from(value: OrcErrv) -> Self { Self::Orc(value) } -} - -pub(crate) async fn encode_command_result( - result: Result, -) -> api::OrcResult { - 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; - /// 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 + '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 + '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, 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) } diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index a23b39e..b8bbe57 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -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> AtomicFeaturesImpl(); + let (typ_id, _) = dyn_cted().inst().card().ops::(); let mut data = enc_vec(&typ_id); self.encode(Pin::<&mut Vec>::new(&mut data)).await; obj_store.objects.read().await.insert(atom_id, Box::new(self)); @@ -121,6 +118,25 @@ impl AtomOps for OwnedAtomOps { AtomCtx(_, id): AtomCtx<'a>, key: Sym, req: Box + '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::().unwrap().free().await + } + matched + }) + } + fn handle_req_ref<'a>( + &'a self, + AtomCtx(_, id): AtomCtx<'a>, + key: Sym, + req: Box + 'a>, ) -> LocalBoxFuture<'a, bool> { Box::pin(async move { let a = AtomReadGuard::new(id.unwrap()).await; @@ -128,9 +144,6 @@ impl AtomOps for OwnedAtomOps { 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 + Any + Clone + 'static { gcl } } - /// Called when this is the final value of the program - fn command(self) -> impl Future { - 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 { async {} } @@ -294,10 +303,10 @@ fn assert_serializable() { pub(crate) trait DynOwnedAtom: DynClone + 'static { fn as_any_ref(&self) -> &dyn Any; + fn as_any(self: Box) -> Box; 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, arg: Expr) -> LocalBoxFuture<'static, GExpr>; - fn dyn_command(self: Box) -> LocalBoxFuture<'static, CmdResult>; fn dyn_free(self: Box) -> LocalBoxFuture<'static, ()>; fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit>; fn dyn_serialize<'a>( @@ -307,6 +316,7 @@ pub(crate) trait DynOwnedAtom: DynClone + 'static { } impl DynOwnedAtom for T { fn as_any_ref(&self) -> &dyn Any { self } + fn as_any(self: Box) -> Box { 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 DynOwnedAtom for T { fn dyn_call(self: Box, arg: Expr) -> LocalBoxFuture<'static, GExpr> { async { self.call(arg).await.to_gen().await }.boxed_local() } - fn dyn_command(self: Box) -> LocalBoxFuture<'static, CmdResult> { Box::pin(self.command()) } fn dyn_free(self: Box) -> LocalBoxFuture<'static, ()> { self.free().boxed_local() } fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit> { async move { self.print_atom(&FmtCtxImpl::default()).await }.boxed_local() diff --git a/orchid-extension/src/atom_thin.rs b/orchid-extension/src/atom_thin.rs index a48c69d..ec2857c 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -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> AtomicFeaturesImpl for A { fn _factory(self) -> AtomFactory { AtomFactory::new(async move || { - let (id, _) = cted().inst().card().ops::(); + let (id, _) = dyn_cted().inst().card().ops::(); let mut buf = enc_vec(&id); self.encode_vec(&mut buf); api::LocalAtom { drop: None, data: api::AtomData(buf) } @@ -63,8 +60,13 @@ impl AtomOps for ThinAtomOps { 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 + '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 + Atomic + Coding async move { bot(err_not_callable(&self.print().await).await) } } #[allow(unused_variables)] - fn command(&self) -> impl Future { - async move { Err(err_not_command(&self.print().await).await.into()) } - } - #[allow(unused_variables)] fn print(&self) -> impl Future { async { format!("ThinAtom({})", type_name::()).into() } } diff --git a/orchid-extension/src/binary.rs b/orchid-extension/src/binary.rs index ac70346..35c894a 100644 --- a/orchid-extension/src/binary.rs +++ b/orchid-extension/src/binary.rs @@ -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; diff --git a/orchid-extension/src/cmd_atom.rs b/orchid-extension/src/cmd_atom.rs new file mode 100644 index 0000000..d34fa68 --- /dev/null +++ b/orchid-extension/src/cmd_atom.rs @@ -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> + DynClone; +} + +pub struct CmdAtom(Box); +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 { MethodSetBuilder::new().handle::() } +} +impl Supports for CmdAtom { + async fn handle<'a>( + &self, + hand: Box + '_>, + req: RunCommand, + ) -> std::io::Result> { + Self(dyn_clone::clone_box(&*self.0)).handle_final(hand, req).await + } + async fn handle_final<'a>( + self, + hand: Box + '_>, + req: RunCommand, + ) -> std::io::Result> { + 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(f: impl AsyncFnOnce() -> Option + Clone + 'static) -> GExpr { + new_atom(CmdAtom(Box::new(|| { + Box::pin(async { + match f().await { + None => None, + Some(r) => Some(r.to_gen().await), + } + }) + }))) +} diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index f687bef..d11727b 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -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>; } @@ -58,6 +59,9 @@ impl TryFromExpr for TAtom { /// 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(pub F); impl> ToExpr for ToExprFuture { @@ -78,6 +84,9 @@ impl> ToExpr for ToExprFuture { self.0.await.to_expr().await } } +impl FusedFuture for ToExprFuture { + fn is_terminated(&self) -> bool { self.0.is_terminated() } +} impl Future for ToExprFuture { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { diff --git a/orchid-extension/src/coroutine_exec.rs b/orchid-extension/src/coroutine_exec.rs index f0deb5c..10d9033 100644 --- a/orchid-extension/src/coroutine_exec.rs +++ b/orchid-extension/src/coroutine_exec.rs @@ -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), @@ -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, 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(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(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, 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(&mut self, val: impl ToExpr) -> OrcRes { let (reply_snd, mut reply_recv) = channel(1); self.0.send(Command::Execute(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR); diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index 9847abb..1e6e2f7 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -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; @@ -44,21 +39,28 @@ task_local::task_local! { } fn get_client() -> Rc { 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(c: Rc, 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: F) -> F::Output { MUTE_REPLY.scope((), f).await } + /// Send a request through the global client's [ClientExt::request] pub async fn request>(t: T) -> T::Response { let response = get_client().request(t).await.unwrap(); @@ -73,7 +75,7 @@ pub async fn notify>(t: T) { get_client().notify(t).await.unwrap() } -pub struct SystemRecord { +struct SystemRecord { cted: CtedObj, } @@ -88,6 +90,7 @@ async fn with_sys_record(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, fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()>; } @@ -108,12 +111,22 @@ task_local! { Rc>) -> Box + 'static> } +/// Handle for a task that is not associated with a particular pending request +/// or past notification pub struct TaskHandle(Box, PhantomData); impl TaskHandle { + /// 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 + 'static>(delay: Duration, f: F) -> TaskHandle { SPAWN.with(|spawn| { TaskHandle(spawn(delay, Box::pin(async { Box::new(f.await) as Box })), PhantomData) @@ -125,24 +138,37 @@ impl DynTaskHandle for Handle> { fn join(self: Box) -> LocalBoxFuture<'static, Box> { 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>, pub context: Vec>, } 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 }) diff --git a/orchid-extension/src/expr.rs b/orchid-extension/src/expr.rs index 7507a24..c5d5fbe 100644 --- a/orchid-extension/src/expr.rs +++ b/orchid-extension/src/expr.rs @@ -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>>>); 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.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, data: Rc>, } impl Expr { + /// Wrap a handle in order to retrieve details about it pub fn from_handle(handle: Rc) -> Self { Self { handle, data: Rc::default() } } + /// Wrap a handle the details of which are already known pub fn from_data(handle: Rc, 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 { 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 { 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(&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, } diff --git a/orchid-extension/src/func_atom.rs b/orchid-extension/src/func_atom.rs index 38c2872..1033ac4 100644 --- a/orchid-extension/src/func_atom.rs +++ b/orchid-extension/src/func_atom.rs @@ -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) -> LocalBoxFuture<'static, OrcRes> + 'static; @@ -32,6 +28,7 @@ task_local! { static ARGV: Vec; } +/// 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) -> impl Iterator { 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: Clone + 'static { fn argtyps() -> &'static [TypeId]; fn apply<'a>(&self, hand: ExecHandle<'a>, v: Vec) -> impl Future>; @@ -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, record: FunRecord, } impl Lambda { + /// Embed a lambda in an Orchid expression pub fn new>(f: F) -> Self { Self { args: vec![], record: process_args(f) } } diff --git a/orchid-extension/src/gen_expr.rs b/orchid-extension/src/gen_expr.rs index 3a6c595..a71b6c9 100644 --- a/orchid-extension/src/gen_expr.rs +++ b/orchid-extension/src/gen_expr.rs @@ -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, Box), + /// Lambda expression. Argument numbers are matched when equal Lambda(u64, Box), + /// 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, Box), + /// 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(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(b: impl ToExpr) -> ToExprFuture> { dyn_lambda(N, b) } +/// A lambda expression. The difference from [lam] is purely aesthetic pub fn dyn_lambda(n: u64, b: impl ToExpr) -> ToExprFuture> { 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], 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; } impl 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, @@ -208,6 +237,7 @@ pub fn call_v( }) } +/// A runtime error pub fn bot(ev: impl IntoIterator) -> GExpr { inherit(GExprKind::Bottom(OrcErrv::new(ev).unwrap())) } diff --git a/orchid-extension/src/interner.rs b/orchid-extension/src/interner.rs index 22934a6..f65bb64 100644 --- a/orchid-extension/src/interner.rs +++ b/orchid-extension/src/interner.rs @@ -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 { Rc::::default() } +pub(crate) fn new_interner() -> Rc { Rc::::default() } diff --git a/orchid-extension/src/lexer.rs b/orchid-extension/src/lexer.rs index 2bbd3c0..f4ccdad 100644 --- a/orchid-extension/src/lexer.rs +++ b/orchid-extension/src/lexer.rs @@ -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, 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`, or `[GenTokTree; usize]` pub trait LexedData { fn into_vec(self) -> Vec; } @@ -82,16 +95,26 @@ impl LexedData for [GenTokTree; N] { fn into_vec(self) -> Vec { 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]; + /// 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>; } +/// Type-erased [Lexer] pub trait DynLexer: Debug + Send + Sync + 'static { + /// Type-erased [Lexer::CHAR_FILTER] fn char_filter(&self) -> &'static [RangeInclusive]; + /// Type-erased [Lexer::lex] fn lex<'a>( &self, tail: &'a str, @@ -110,4 +133,6 @@ impl DynLexer for T { } } +/// Type-erased instance of a lexer that is returned by +/// [crate::System::lexers] pub type LexerObj = &'static dyn DynLexer; diff --git a/orchid-extension/src/lib.rs b/orchid-extension/src/lib.rs index f5f4694..898b17f 100644 --- a/orchid-extension/src/lib.rs +++ b/orchid-extension/src/lib.rs @@ -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; diff --git a/orchid-extension/src/logger.rs b/orchid-extension/src/logger.rs index ed0ab9a..0dea028 100644 --- a/orchid-extension/src/logger.rs +++ b/orchid-extension/src/logger.rs @@ -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, routing: HashMap, } diff --git a/orchid-extension/src/other_system.rs b/orchid-extension/src/other_system.rs index a50f1cf..b2713bc 100644 --- a/orchid-extension/src/other_system.rs +++ b/orchid-extension/src/other_system.rs @@ -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 { - pub(crate) card: C, + pub(crate) _card: C, pub(crate) id: api::SysId, } impl SystemHandle { - 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 Clone for SystemHandle { 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; } impl DynSystemHandle for SystemHandle { fn id(&self) -> api::SysId { self.id } - fn get_card(&self) -> &dyn DynSystemCard { &self.card } + fn get_card(&self) -> Box { 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>(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_id() + } else { + (cted.deps().find(|s| s.get_card().type_id() == TypeId::of::())) + .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() } diff --git a/orchid-extension/src/parser.rs b/orchid-extension/src/parser.rs index d92f1a2..5131fac 100644 --- a/orchid-extension/src/parser.rs +++ b/orchid-extension/src/parser.rs @@ -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; +/// [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; +/// 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) -> Vec { 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>> + '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 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, + /// 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, @@ -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, @@ -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) -> Vec { 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), } +/// 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, use_prelude: bool }, + /// A module with additional descendants + Mod { + /// Logical lines inside the module + lines: Vec, + /// 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 + 'b, @@ -202,6 +268,7 @@ impl ConstCtx { } }) } + /// Static-length version of [Self::names] pub async fn names_n(&self, names: [&Sym; N]) -> [OrcRes; N] { self.names(names).collect::>().await.try_into().expect("Lengths must match") } diff --git a/orchid-extension/src/reflection.rs b/orchid-extension/src/reflection.rs index d069989..c2dfac2 100644 --- a/orchid-extension/src/reflection.rs +++ b/orchid-extension/src/reflection.rs @@ -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, kind: ReflMemKind, } +/// Potentially partial reflected information about a member inside a module #[derive(Clone, Debug)] pub struct ReflMem(Rc); 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, path: VPath, members: MemoMap, } +/// A module whose members can be listed and inspected #[derive(Clone, Debug)] pub struct ReflMod(Rc); 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 { 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 { 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> } +/// 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)) } diff --git a/orchid-extension/src/stream_reqs.rs b/orchid-extension/src/std_reqs.rs similarity index 68% rename from orchid-extension/src/stream_reqs.rs rename to orchid-extension/src/std_reqs.rs index fffba78..62ea3f0 100644 --- a/orchid-extension/src/stream_reqs.rs +++ b/orchid-extension/src/std_reqs.rs @@ -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; +} +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", } } } diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index b1272c8..67a7e85 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -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>>; -} - -/// 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>>; -} - -impl DynSystemCardExt for T {} -pub(crate) trait DynSystemCardExt: DynSystemCard { - fn ops_by_tid(&self, tid: TypeId) -> Option<(AtomTypeId, Box)> { - (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> { - 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(&self) -> (AtomTypeId, Box) { - self - .ops_by_tid(TypeId::of::()) - .unwrap_or_else(|| panic!("{} is not an atom in {}", type_name::(), 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>> { - [Some(Fun::ops()), Some(Lambda::ops()), Some(Replier::ops())].into_iter() -} - -impl DynSystemCard for T { - fn name(&self) -> &'static str { T::Ctor::NAME } - fn atoms(&'_ self) -> BoxedIter<'_, Option>> { - Box::new(Self::atoms().into_iter()) - } -} +pub type CardForSystem = <::Ctor as SystemCtor>::Card; +pub type ReqForSystem = as SystemCard>::Req; /// System as defined by author -pub trait System: SystemCard + 'static { +pub trait System: Debug + 'static { + type Ctor: SystemCtor; fn prelude(&self) -> impl Future>; fn env(&self) -> impl Future>; fn lexers(&self) -> Vec; @@ -80,11 +23,11 @@ pub trait System: SystemCard + 'static { fn request<'a>( &self, hand: Box + 'a>, - req: Self::Req, + req: ReqForSystem, ) -> impl Future>; } -pub trait DynSystem: DynSystemCard + 'static { +pub trait DynSystem: Debug + 'static { fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec>; fn dyn_env(&self) -> LocalBoxFuture<'_, Vec>; fn dyn_lexers(&self) -> Vec; @@ -93,7 +36,7 @@ pub trait DynSystem: DynSystemCard + 'static { &'a self, hand: Box + 'b>, ) -> LocalBoxFuture<'a, Receipt<'b>>; - fn card(&self) -> &dyn DynSystemCard; + fn card(&self) -> Box; } impl DynSystem for T { @@ -106,11 +49,11 @@ impl DynSystem for T { mut hand: Box + 'b>, ) -> LocalBoxFuture<'a, Receipt<'b>> { Box::pin(async move { - let value = hand.read_req::<::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 { Box::new(CardForSystem::::default()) } } #[derive(Clone)] @@ -125,23 +68,7 @@ pub(crate) async fn with_sys(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>(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_id() - } else { - (cted.deps().find(|s| s.get_card().type_id() == TypeId::of::())) - .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() -> Cted { + Rc::downcast::>(dyn_cted().as_any()).unwrap().as_ref().clone() } diff --git a/orchid-extension/src/system_card.rs b/orchid-extension/src/system_card.rs new file mode 100644 index 0000000..19e958e --- /dev/null +++ b/orchid-extension/src/system_card.rs @@ -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>>; +} + +/// 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>>; +} + +impl DynSystemCard for T { + fn name(&self) -> &'static str { T::Ctor::NAME } + fn atoms(&'_ self) -> BoxedIter<'_, Option>> { + Box::new(Self::atoms().into_iter()) + } +} + +impl DynSystemCardExt for T {} +pub(crate) trait DynSystemCardExt: DynSystemCard { + fn ops_by_tid(&self, tid: TypeId) -> Option<(AtomTypeId, Box)> { + (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> { + 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(&self) -> (AtomTypeId, Box) { + self + .ops_by_tid(TypeId::of::()) + .unwrap_or_else(|| panic!("{} is not an atom in {}", type_name::(), 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>> { + [Some(Fun::ops()), Some(Lambda::ops()), Some(Replier::ops())].into_iter() +} diff --git a/orchid-extension/src/system_ctor.rs b/orchid-extension/src/system_ctor.rs index f5c5b66..407f802 100644 --- a/orchid-extension/src/system_ctor.rs +++ b/orchid-extension/src/system_ctor.rs @@ -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 { pub deps: ::Sat, - pub inst: Arc, + pub inst: Rc, } impl Clone for Cted { 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) -> Rc; fn deps<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>; - fn inst(&self) -> Arc; + fn inst(&self) -> Rc; } impl DynCted for Cted { - fn as_any(&self) -> &dyn Any { self } + fn as_any(self: Rc) -> Rc { self } fn deps<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)> { self.deps.iter() } - fn inst(&self) -> Arc { self.inst.clone() } + fn inst(&self) -> Rc { self.inst.clone() } } -pub type CtedObj = Arc; +pub type CtedObj = Rc; 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; + type Card: SystemCard; const NAME: &'static str; const VERSION: f64; /// Create a system instance. @@ -85,8 +84,8 @@ impl 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:: { deps, inst }) + let inst = Rc::new(self.inst(deps.clone())); + Rc::new(Cted:: { deps, inst }) } } diff --git a/orchid-extension/src/tokio.rs b/orchid-extension/src/tokio.rs index e06466d..8075a79 100644 --- a/orchid-extension/src/tokio.rs +++ b/orchid-extension/src/tokio.rs @@ -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 } }; } diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index 8e7717e..d189b8e 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -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; +/// Tokens generated by lexers and parsers - without location data +/// +/// See: [GenTokTree], [Token], [crate::Lexer], [crate::Parser] pub type GenTok = Token; impl TokenVariant for GExpr { @@ -41,9 +44,15 @@ impl TokenVariant 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 { 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 { 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>, ) -> Vec { - 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>) -> (String, MemKind) { - (name.to_string(), MemKind::module(mems)) -} +/// A Rust function that is passed to Orchid via [Fun] pub fn fun(public: bool, name: &str, xf: impl ExprFunc) -> Vec { 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>) -> Vec { 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>) -> Ve } items } - +/// Add comments to a set of members pub fn comments<'a>( cmts: impl IntoIterator, mut val: Vec, @@ -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>) -> Vec { - let mut all_members = HashMap::)>::new(); + let mut all_members = HashMap::)>::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); +pub(crate) struct LazyMemberFactory(Box); 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, + pub(crate) name: String, + pub(crate) kind: LazyMemKind, + pub(crate) public: bool, + pub(crate) comments: Vec, } 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), - Lazy(LazyMemberFactory), } impl MemKind { pub async fn cnst(val: impl ToExpr) -> Self { Self::Const(val.to_gen().await) } pub fn module(mems: impl IntoIterator>) -> Self { Self::Mod(mems.into_iter().flatten().collect()) } +} + +pub(crate) enum LazyMemKind { + Const(GExpr), + Mod(Vec), + 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, 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; } -pub struct TreeIntoApiCtxImpl<'a> { +pub(crate) struct TreeIntoApiCtxImpl<'a> { pub basepath: &'a [IStr], pub path: Substack<'a, IStr>, } diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 6208cac..7958c06 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -27,6 +27,7 @@ orchid-extension = { version = "0.1.0", path = "../orchid-extension", optional = ordered-float = "5.1.0" pastey = "0.2.1" substack = "1.1.1" +task-local = "0.1.0" tokio = { version = "1.49.0", features = ["process"], optional = true } tokio-util = { version = "0.7.18", features = ["compat"], optional = true } trait-set = "0.3.0" diff --git a/orchid-host/src/atom.rs b/orchid-host/src/atom.rs index cbf65ac..f2f76b4 100644 --- a/orchid-host/src/atom.rs +++ b/orchid-host/src/atom.rs @@ -94,7 +94,7 @@ impl AtomHand { #[must_use] pub fn ext(&self) -> &Extension { self.sys().ext() } pub async fn req(&self, key: api::TStrv, req: Vec) -> Option> { - self.0.owner.client().request(api::Fwded(self.0.api_ref(), key, req)).await.unwrap() + self.0.owner.client().request(api::FinalFwded(self.0.api_ref(), key, req)).await.unwrap() } #[must_use] pub fn api_ref(&self) -> api::Atom { self.0.api_ref() } diff --git a/orchid-host/src/cmd_system.rs b/orchid-host/src/cmd_system.rs new file mode 100644 index 0000000..7172c9e --- /dev/null +++ b/orchid-host/src/cmd_system.rs @@ -0,0 +1,249 @@ +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::VecDeque; +use std::fmt::Debug; +use std::pin::pin; +use std::rc::Rc; + +use async_event::Event; +use async_fn_stream::stream; +use futures::channel::mpsc; +use futures::future::LocalBoxFuture; +use futures::stream::FuturesUnordered; +use futures::{SinkExt, StreamExt, select}; +use never::Never; +use orchid_base::{OrcErrv, Receipt, ReqHandle, Sym}; +use orchid_extension::{self as ox, AtomicFeatures as _}; + +use crate::ctx::Ctx; +use crate::execute::{ExecCtx, ExecResult}; +use crate::expr::{Expr, ExprFromApiCtx, PathSetBuilder}; +use crate::tree::Root; + +struct CommandQueueState { + new: VecDeque, + added: Rc, + wants_exit: bool, + ctx: Ctx, +} +#[derive(Clone)] +struct CommandQueue(Rc>); +impl CommandQueue { + fn new(ctx: Ctx, init: impl IntoIterator) -> Self { + Self(Rc::new(RefCell::new(CommandQueueState { + new: init.into_iter().collect(), + added: Rc::default(), + wants_exit: false, + ctx, + }))) + } + pub fn push(&self, expr: Expr) { + let was_empty = { + let mut g = self.0.borrow_mut(); + g.new.push_back(expr); + g.new.len() == 1 + }; + if was_empty { + let added = self.0.borrow_mut().added.clone(); + added.notify_one(); + } + } + pub async fn get_new(&self) -> Expr { + let added = { + let mut g = self.0.borrow_mut(); + if let Some(waiting) = g.new.pop_front() { + return waiting; + } + g.added.clone() + }; + added.wait_until(|| self.0.borrow_mut().new.pop_front()).await + } +} +impl Debug for CommandQueue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CommandQueue").finish_non_exhaustive() + } +} + +pub enum CmdResult { + /// All command sequences settled + Settled, + /// Exit was requested explicitly by usercode + Exit, + /// Ran out of gas + Gas, + /// Received a value that wasn't a command + /// + /// This is potentially user error, but implementors may choose to well-define + /// certain responses + NonCommand(Expr), + /// Received an Orchid error + /// + /// This is definitely user error, but implementors may choose to continue + /// running other chains of execution after handling it + Err(OrcErrv), +} + +pub struct CmdRunner { + root: Root, + queue: CommandQueue, + gas: Option, + interrupted: Option, + futures: FuturesUnordered>>, +} +impl CmdRunner { + pub async fn new(root: Root, ctx: Ctx, init: impl IntoIterator) -> Self { + Self { + futures: FuturesUnordered::new(), + gas: None, + root, + interrupted: None, + queue: CommandQueue::new(ctx, init), + } + } + #[must_use] + pub fn get_gas(&self) -> u64 { self.gas.expect("queried gas but no gas was set") } + pub fn set_gas(&mut self, gas: u64) { self.gas = Some(gas) } + pub fn disable_gas(&mut self) { self.gas = None } + pub async fn execute(&mut self) -> CmdResult { + let waiting_on_queue = RefCell::new(false); + let (mut spawn, mut on_spawn) = mpsc::channel(1); + let mut normalize_stream = pin!( + stream(async |mut h| { + loop { + if self.queue.0.borrow().wants_exit { + h.emit(CmdResult::Exit).await; + break; + } + waiting_on_queue.replace(false); + let mut xctx = match self.interrupted.take() { + None => ExecCtx::new(self.root.clone(), self.queue.get_new().await).await, + Some(xctx) => xctx, + }; + waiting_on_queue.replace(true); + xctx.set_gas(self.gas); + let res = xctx.execute().await; + match res { + ExecResult::Err(e, gas) => { + self.gas = gas; + h.emit(CmdResult::Err(e)).await; + }, + ExecResult::Gas(exec) => { + self.interrupted = Some(exec); + h.emit(CmdResult::Gas).await; + }, + ExecResult::Value(val, gas) => { + self.gas = gas; + let Some(atom) = val.as_atom().await else { + h.emit(CmdResult::NonCommand(val)).await; + continue; + }; + let queue = self.queue.clone(); + let ctx = queue.0.borrow_mut().ctx.clone(); + spawn + .send(Box::pin(async move { + match atom.ipc(ox::std_reqs::RunCommand).await { + None => Some(CmdResult::NonCommand(val)), + Some(None) => None, + Some(Some(expr)) => { + let from_api_cx = ExprFromApiCtx { ctx, sys: atom.api_ref().owner }; + queue.push(Expr::from_api(expr, PathSetBuilder::new(), from_api_cx).await); + None + }, + } + })) + .await + .expect("Receiver is owned by the layer that polls this"); + }, + } + } + }) + .fuse() + ); + loop { + if self.queue.0.borrow().wants_exit { + break CmdResult::Exit; + } + let task = select!( + r_opt = self.futures.by_ref().next() => match r_opt { + Some(Some(r)) => break r, + None if *waiting_on_queue.borrow() => break CmdResult::Settled, + None | Some(None) => continue, + }, + r = normalize_stream.by_ref().next() => break r.expect("infinite stream"), + task = on_spawn.by_ref().next() => task.expect("sender moved into infinite stream"), + ); + self.futures.push(task) + } + } +} + +#[derive(Default, Debug)] +pub struct CmdSystemCard; +impl ox::SystemCard for CmdSystemCard { + type Ctor = CmdSystemCtor; + type Req = Never; + fn atoms() -> impl IntoIterator>> { + [Some(CommandQueue::ops())] + } +} + +#[derive(Debug)] +pub struct CmdSystemCtor { + queue: CommandQueue, +} +impl ox::SystemCtor for CmdSystemCtor { + const NAME: &'static str = "orchid::cmd"; + const VERSION: f64 = 0.1; + type Card = CmdSystemCard; + type Deps = (); + type Instance = CmdSystemInst; + fn inst(&self, _: ::Sat) -> Self::Instance { + CmdSystemInst { queue: self.queue.clone() } + } +} + +fn ox_get_queue() -> CommandQueue { ox::cted::().inst.queue.clone() } + +#[derive(Debug)] +pub struct CmdSystemInst { + queue: CommandQueue, +} +impl ox::System for CmdSystemInst { + type Ctor = CmdSystemCtor; + async fn env(&self) -> Vec { + ox::tree::prefix("orchid::cmd", [ + ox::tree::cnst(false, "queue", ox::gen_expr::new_atom(ox_get_queue())), + ox::tree::fun(true, "spawn", async |side: ox::Expr, cont: ox::Expr| { + ox::cmd(async move || { + let queue = ox_get_queue(); + let side_xtk = side.serialize().await; + let mut g = queue.0.borrow_mut(); + let host_ex = + g.ctx.exprs.take_expr(side_xtk).expect("Host could not locate leaked expr by ID "); + g.new.push_back(host_ex); + Some(cont) + }) + }), + ]) + } + async fn prelude(&self) -> Vec { vec![] } + fn lexers(&self) -> Vec { vec![] } + fn parsers(&self) -> Vec { vec![] } + async fn request<'a>( + &self, + _hand: Box + 'a>, + req: ox::ReqForSystem, + ) -> Receipt<'a> { + match req {} + } +} + +impl ox::Atomic for CommandQueue { + type Data = (); + type Variant = ox::OwnedVariant; +} +impl ox::OwnedAtom for CommandQueue { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } +} diff --git a/orchid-host/src/execute.rs b/orchid-host/src/execute.rs index 5965d30..31f3697 100644 --- a/orchid-host/src/execute.rs +++ b/orchid-host/src/execute.rs @@ -20,9 +20,9 @@ enum StackOp { } pub enum ExecResult { - Value(Expr), + Value(Expr, Option), Gas(ExecCtx), - Err(OrcErrv), + Err(OrcErrv, Option), } pub struct ExecCtx { @@ -46,17 +46,6 @@ impl ExecCtx { #[must_use] pub fn idle(&self) -> bool { self.did_pop } #[must_use] - pub fn result(self) -> ExecResult { - if self.idle() { - match &*self.cur { - ExprKind::Bottom(errv) => ExecResult::Err(errv.clone()), - _ => ExecResult::Value(*self.cur.unbind()), - } - } else { - ExecResult::Gas(self) - } - } - #[must_use] pub fn use_gas(&mut self, amount: u64) -> bool { if let Some(gas) = &mut self.gas { *gas -= amount; @@ -79,7 +68,7 @@ impl ExecCtx { Err(TryLockError) => panic!("Cycle encountered!"), } } - pub async fn execute(&mut self) { + pub async fn execute(mut self) -> ExecResult { while self.use_gas(1) { let mut kind_swap = ExprKind::Missing; mem::swap(&mut kind_swap, &mut self.cur); @@ -135,7 +124,7 @@ impl ExecCtx { StackOp::Nop => (), StackOp::Pop => match self.stack.pop() { Some(top) => self.cur = top, - None => return, + None => return ExecResult::Value(*self.cur.unbind(), self.gas), }, StackOp::Push(sub) => { self.cur_pos = sub.pos(); @@ -150,10 +139,11 @@ impl ExecCtx { } *self.cur = ExprKind::Bottom(err.clone()); self.stack = vec![]; - return; + return ExecResult::Err(err.clone(), self.gas); }, } } + ExecResult::Gas(self) } } diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index e95025b..6a2e075 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -162,8 +162,10 @@ impl Extension { let sys = ctx.system_inst(atom.owner).await.expect("owner of live atom dropped"); let client = sys.client(); - let reply = - client.request(api::Fwded(fw.0.clone(), *key, body.clone())).await.unwrap(); + let reply = client + .request(api::FinalFwded(fw.0.clone(), *key, body.clone())) + .await + .unwrap(); handle.reply(fw, &reply).await }, api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => { diff --git a/orchid-host/src/inline.rs b/orchid-host/src/inline.rs index b8b6c89..9d91307 100644 --- a/orchid-host/src/inline.rs +++ b/orchid-host/src/inline.rs @@ -1,28 +1,18 @@ -#[cfg(feature = "orchid-extension")] +use std::io; +use std::rc::Rc; use std::time::Duration; -#[cfg(feature = "orchid-extension")] -use orchid_base::on_drop; -#[cfg(feature = "orchid-extension")] +use futures::io::BufReader; +use futures::{AsyncBufReadExt, StreamExt}; +use orchid_base::{log, on_drop}; use orchid_extension as ox; +use unsync_pipe::pipe; -#[cfg(feature = "orchid-extension")] use crate::ctx::Ctx; -#[cfg(feature = "orchid-extension")] use crate::extension::ExtPort; -#[cfg(feature = "orchid-extension")] use crate::task_set::TaskSet; -#[cfg(feature = "orchid-extension")] -pub async fn ext_inline(builder: ox::entrypoint::ExtensionBuilder, ctx: Ctx) -> ExtPort { - use std::io; - use std::rc::Rc; - - use futures::io::BufReader; - use futures::{AsyncBufReadExt, StreamExt}; - use orchid_base::log; - use unsync_pipe::pipe; - +pub async fn ext_inline(builder: ox::ExtensionBuilder, ctx: Ctx) -> ExtPort { let (in_stdin, out_stdin) = pipe(1024); let (in_stdout, out_stdout) = pipe(1024); let (in_stderr, out_stderr) = pipe(1024); @@ -48,7 +38,7 @@ pub async fn ext_inline(builder: ox::entrypoint::ExtensionBuilder, ctx: Ctx) -> std::mem::drop(ctx.clone().spawn(Duration::ZERO, async move { let task_set2 = task_set1.clone(); builder - .run(ox::ext_port::ExtPort { + .run(ox::ExtPort { input: Box::pin(out_stdin), output: Box::pin(in_stdout), log: Box::pin(in_stderr), diff --git a/orchid-host/src/lib.rs b/orchid-host/src/lib.rs index 24633dd..db26601 100644 --- a/orchid-host/src/lib.rs +++ b/orchid-host/src/lib.rs @@ -2,6 +2,8 @@ use orchid_api as api; pub mod atom; pub mod ctx; +#[cfg(feature = "orchid-extension")] +pub mod cmd_system; pub mod dealias; #[cfg(feature = "tokio")] pub mod dylib; @@ -9,6 +11,7 @@ pub mod execute; pub mod expr; pub mod expr_store; pub mod extension; +#[cfg(feature = "orchid-extension")] pub mod inline; pub mod lex; pub mod logger; diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs index 67c0113..a79931d 100644 --- a/orchid-std/src/lib.rs +++ b/orchid-std/src/lib.rs @@ -13,11 +13,10 @@ pub use std::tuple::{HomoTpl, Tpl, Tuple, UntypedTuple}; pub use macros::macro_system::MacroSystem; pub use macros::mactree::{MacTok, MacTree}; use orchid_api as api; -use orchid_extension::dylib_main; -use orchid_extension::entrypoint::ExtensionBuilder; +use orchid_extension::{ExtensionBuilder, dylib_main}; pub fn builder() -> ExtensionBuilder { - ExtensionBuilder::new("orchid-std::main").system(StdSystem::default()).system(MacroSystem) + ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem) } dylib_main! { builder() } diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs index 3810311..0856338 100644 --- a/orchid-std/src/macros/instantiate_tpl.rs +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -2,11 +2,9 @@ use std::borrow::Cow; use never::Never; use orchid_base::fmt; -use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::expr::Expr; +use orchid_extension::Expr; use orchid_extension::gen_expr::new_atom; -use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom}; +use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom, ToExpr, exec}; use crate::macros::mactree::{MacTok, MacTree}; diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index e3b3c16..470e7e8 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -7,10 +7,10 @@ use orchid_base::{ Comment, OrcRes, Paren, Parsed, Snippet, Sym, expect_tok, is, report, sym, token_errv, try_pop_no_fluff, with_reporter, }; -use orchid_extension::TAtom; -use orchid_extension::conv::TryFromExpr; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{ + ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser, TAtom, TryFromExpr, +}; use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq}; use crate::macros::ph_lexer::PhAtom; diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs index 6ed97cf..5957d5c 100644 --- a/orchid-std/src/macros/macro_lib.rs +++ b/orchid-std/src/macros/macro_lib.rs @@ -1,8 +1,7 @@ use orchid_base::sym; -use orchid_extension::TAtom; -use orchid_extension::coroutine_exec::exec; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; +use orchid_extension::{TAtom, exec}; use crate::macros::mactree::MacTree; use crate::macros::resolve::resolve; diff --git a/orchid-std/src/macros/macro_line.rs b/orchid-std/src/macros/macro_line.rs index d36cf78..869282b 100644 --- a/orchid-std/src/macros/macro_line.rs +++ b/orchid-std/src/macros/macro_line.rs @@ -9,9 +9,9 @@ use orchid_base::{ mk_errv, report, sym, token_errv, try_pop_no_fluff, with_reporter, }; use orchid_extension::TAtom; -use orchid_extension::conv::{ToExpr, TryFromExpr}; +use orchid_extension::{ToExpr, TryFromExpr}; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::macros::let_line::{dealias_mac_v, parse_tokv}; use crate::macros::macro_value::{Macro, MacroData, Rule}; diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs index 60925f4..9e5b216 100644 --- a/orchid-std/src/macros/macro_system.rs +++ b/orchid-std/src/macros/macro_system.rs @@ -1,12 +1,9 @@ use never::Never; use orchid_base::{Receipt, ReqHandle, Sym, sym}; -use orchid_extension::{AtomOps, AtomicFeatures}; -use orchid_extension::lexer::LexerObj; -use orchid_extension::other_system::SystemHandle; -use orchid_extension::parser::ParserObj; -use orchid_extension::system::{System, SystemCard}; -use orchid_extension::system_ctor::SystemCtor; use orchid_extension::tree::{GenMember, merge_trivial}; +use orchid_extension::{ + AtomOps, AtomicFeatures, LexerObj, ParserObj, System, SystemCard, SystemCtor, SystemHandle, +}; use crate::macros::instantiate_tpl::InstantiateTplCall; use crate::macros::let_line::LetLine; @@ -24,10 +21,11 @@ use crate::{MacTree, StdSystem}; pub struct MacroSystem; impl SystemCtor for MacroSystem { type Deps = StdSystem; - type Instance = Self; + type Instance = MacroSystemInst; + type Card = Self; const NAME: &'static str = "orchid::macros"; const VERSION: f64 = 0.00_01; - fn inst(&self, _: SystemHandle) -> Self::Instance { Self } + fn inst(&self, std: SystemHandle) -> Self::Instance { MacroSystemInst { _std: std } } } impl SystemCard for MacroSystem { type Ctor = Self; @@ -43,7 +41,13 @@ impl SystemCard for MacroSystem { ] } } -impl System for MacroSystem { + +#[derive(Debug)] +pub struct MacroSystemInst { + _std: SystemHandle, +} +impl System for MacroSystemInst { + type Ctor = MacroSystem; async fn request<'a>(&self, _: Box + 'a>, req: Never) -> Receipt<'a> { match req {} } diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs index 044c983..a809967 100644 --- a/orchid-std/src/macros/mactree.rs +++ b/orchid-std/src/macros/mactree.rs @@ -10,7 +10,7 @@ use orchid_base::{ FmtCtx, FmtUnit, Format, IStr, OrcErrv, Paren, Pos, Sym, Variants, indent, tl_cache, }; use orchid_extension::Atomic; -use orchid_extension::expr::Expr; +use orchid_extension::Expr; use orchid_extension::{OwnedAtom, OwnedVariant}; fn union_rc_sets(seq: impl IntoIterator>>) -> Rc> { diff --git a/orchid-std/src/macros/mactree_lexer.rs b/orchid-std/src/macros/mactree_lexer.rs index e201737..2402722 100644 --- a/orchid-std/src/macros/mactree_lexer.rs +++ b/orchid-std/src/macros/mactree_lexer.rs @@ -5,8 +5,8 @@ use itertools::chain; use orchid_base::Paren; use orchid_base::{OrcRes, PARENS, is, mk_errv}; use orchid_extension::gen_expr::new_atom; -use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; -use orchid_extension::parser::p_tree2gen; +use orchid_extension::{LexContext, Lexer, err_not_applicable}; +use orchid_extension::p_tree2gen; use orchid_extension::tree::{GenTok, GenTokTree, x_tok}; use crate::macros::instantiate_tpl::InstantiateTplCall; diff --git a/orchid-std/src/macros/match_macros.rs b/orchid-std/src/macros/match_macros.rs index edec506..fe0d4cb 100644 --- a/orchid-std/src/macros/match_macros.rs +++ b/orchid-std/src/macros/match_macros.rs @@ -6,9 +6,9 @@ use futures::{Stream, StreamExt, stream}; use never::Never; use orchid_api_derive::Coding; use orchid_base::{OrcRes, Sym, fmt, is, mk_errv, sym}; -use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::{ExecHandle, exec}; -use orchid_extension::expr::{Expr, ExprHandle}; +use orchid_extension::ToExpr; +use orchid_extension::{ExecHandle, exec}; +use orchid_extension::{Expr, ExprHandle}; use orchid_extension::gen_expr::{GExpr, arg, bot, call, call_v, lam, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom}; diff --git a/orchid-std/src/macros/ph_lexer.rs b/orchid-std/src/macros/ph_lexer.rs index 7678d29..6306e0e 100644 --- a/orchid-std/src/macros/ph_lexer.rs +++ b/orchid-std/src/macros/ph_lexer.rs @@ -2,7 +2,7 @@ use orchid_api_derive::Coding; use orchid_base::{FmtUnit, OrcRes, es, is, mk_errv, name_char, name_start}; use orchid_extension::Atomic; use orchid_extension::gen_expr::new_atom; -use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; +use orchid_extension::{LexContext, Lexer, err_not_applicable}; use orchid_extension::tree::{GenTokTree, x_tok}; use orchid_extension::{ThinAtom, ThinVariant}; diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index 0d5c757..0847778 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -6,11 +6,8 @@ use futures::{FutureExt, StreamExt, stream}; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; use orchid_base::{NameLike, Paren, Pos, Sym, VPath, fmt, is, log, mk_errv}; -use orchid_extension::TAtom; -use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::exec; use orchid_extension::gen_expr::{GExpr, arg, bot, call, call_v, dyn_lambda, new_atom}; -use orchid_extension::reflection::{ReflMemKind, refl}; +use orchid_extension::{ReflMemKind, TAtom, ToExpr, exec, refl}; use subslice_offset::SubsliceOffset; use substack::Substack; diff --git a/orchid-std/src/macros/stdlib/option.rs b/orchid-std/src/macros/stdlib/option.rs index 8d1d11c..0d12223 100644 --- a/orchid-std/src/macros/stdlib/option.rs +++ b/orchid-std/src/macros/stdlib/option.rs @@ -1,11 +1,8 @@ use futures::StreamExt; use orchid_base::sym; -use orchid_extension::TAtom; -use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::expr::Expr; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; +use orchid_extension::{Expr, TAtom, ToExpr, exec}; use crate::macros::match_macros::MatcherAtom; use crate::macros::resolve::resolve; diff --git a/orchid-std/src/macros/stdlib/record.rs b/orchid-std/src/macros/stdlib/record.rs index ba18384..cd27344 100644 --- a/orchid-std/src/macros/stdlib/record.rs +++ b/orchid-std/src/macros/stdlib/record.rs @@ -1,8 +1,8 @@ use orchid_base::sym; use orchid_extension::TAtom; -use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::expr::Expr; +use orchid_extension::ToExpr; +use orchid_extension::exec; +use orchid_extension::Expr; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, prefix}; diff --git a/orchid-std/src/macros/stdlib/tuple.rs b/orchid-std/src/macros/stdlib/tuple.rs index 7efc692..297fa7b 100644 --- a/orchid-std/src/macros/stdlib/tuple.rs +++ b/orchid-std/src/macros/stdlib/tuple.rs @@ -1,9 +1,9 @@ use futures::{StreamExt, stream}; use orchid_base::{OrcRes, sym}; use orchid_extension::TAtom; -use orchid_extension::conv::ToExpr; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::expr::Expr; +use orchid_extension::ToExpr; +use orchid_extension::exec; +use orchid_extension::Expr; use orchid_extension::gen_expr::{GExpr, call, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; diff --git a/orchid-std/src/macros/utils.rs b/orchid-std/src/macros/utils.rs index 3beafa2..27dbd0a 100644 --- a/orchid-std/src/macros/utils.rs +++ b/orchid-std/src/macros/utils.rs @@ -7,7 +7,7 @@ use futures::future::LocalBoxFuture; use itertools::{Itertools, chain}; use never::Never; use orchid_base::{NameLike, Sym, VPath, is}; -use orchid_extension::conv::ToExpr; +use orchid_extension::ToExpr; use orchid_extension::gen_expr::{GExpr, new_atom}; use orchid_extension::tree::{GenMember, MemKind, cnst, lazy}; use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom}; @@ -32,13 +32,13 @@ impl Atomic for MacroBodyArgCollector { impl OwnedAtom for MacroBodyArgCollector { type Refs = Never; async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn call_ref(&self, arg: orchid_extension::expr::Expr) -> GExpr { + async fn call_ref(&self, arg: orchid_extension::Expr) -> GExpr { if !self.args.is_empty() { eprintln!("This is an intermediary value. It should never be copied"); } self.clone().call(arg).await } - async fn call(mut self, arg: orchid_extension::expr::Expr) -> GExpr { + async fn call(mut self, arg: orchid_extension::Expr) -> GExpr { let atom = (TAtom::::downcast(arg.handle()).await).unwrap_or_else(|_| { panic!("This is an intermediary value, the argument types are known in advance") }); diff --git a/orchid-std/src/std/binary/binary_lib.rs b/orchid-std/src/std/binary/binary_lib.rs index f0ea6c5..e8bcd95 100644 --- a/orchid-std/src/std/binary/binary_lib.rs +++ b/orchid-std/src/std/binary/binary_lib.rs @@ -1,10 +1,9 @@ use std::rc::Rc; use orchid_base::{OrcErrv, is, mk_errv}; -use orchid_extension::TAtom; -use orchid_extension::func_atom::get_arg_posv; use orchid_extension::gen_expr::new_atom; use orchid_extension::tree::{GenMember, comments, fun, prefix}; +use orchid_extension::{TAtom, get_arg_posv}; use crate::std::binary::binary_atom::BlobAtom; use crate::std::boolean::Bool; diff --git a/orchid-std/src/std/boolean.rs b/orchid-std/src/std/boolean.rs index 90bf7e3..3bb5f1f 100644 --- a/orchid-std/src/std/boolean.rs +++ b/orchid-std/src/std/boolean.rs @@ -1,7 +1,7 @@ use orchid_api_derive::Coding; use orchid_base::{FmtUnit, OrcRes, sym}; -use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::expr::Expr; +use orchid_extension::{ToExpr, TryFromExpr}; +use orchid_extension::Expr; use orchid_extension::gen_expr::GExpr; use orchid_extension::tree::{GenMember, cnst, comments, fun, prefix}; use orchid_extension::{Atomic, TAtom, ThinAtom, ThinVariant}; diff --git a/orchid-std/src/std/future/future_lib.rs b/orchid-std/src/std/future/future_lib.rs index 3d1ab43..352215f 100644 --- a/orchid-std/src/std/future/future_lib.rs +++ b/orchid-std/src/std/future/future_lib.rs @@ -19,15 +19,14 @@ use never::Never; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::{FmtCtxImpl, OrcRes}; -use orchid_extension::conv::ToExpr; +use orchid_extension::ToExpr; use orchid_extension::entrypoint::spawn; -use orchid_extension::expr::Expr; +use orchid_extension::Expr; use orchid_extension::gen_expr::{GExpr, IntoGExprStream, call, new_atom}; use orchid_extension::system::cted; use orchid_extension::tree::{GenMember, cnst, comments, fun, prefix}; use orchid_extension::{ - Atomic, CmdResult, Continuation, ForeignAtom, Next, OwnedAtom, OwnedVariant, err_not_callable, - err_not_command, + Atomic, ForeignAtom, OwnedAtom, OwnedVariant, err_not_callable, err_not_command, }; use rust_decimal::prelude::Zero; use tokio::task::{JoinHandle, spawn_local}; diff --git a/orchid-std/src/std/future/mod.rs b/orchid-std/src/std/future/mod.rs index 59e5c02..853d551 100644 --- a/orchid-std/src/std/future/mod.rs +++ b/orchid-std/src/std/future/mod.rs @@ -1 +1 @@ -pub mod future_lib; +// pub mod future_lib; diff --git a/orchid-std/src/std/number/num_atom.rs b/orchid-std/src/std/number/num_atom.rs index af7ae84..6ebdaef 100644 --- a/orchid-std/src/std/number/num_atom.rs +++ b/orchid-std/src/std/number/num_atom.rs @@ -3,10 +3,10 @@ use std::io; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::{FmtUnit, Numeric, OrcRes, Receipt, ReqHandle, ReqHandleExt, Sym, sym}; -use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::expr::Expr; -use orchid_extension::system::sys_req; -use orchid_extension::{Atomic, MethodSetBuilder, Supports, TAtom, ThinAtom, ThinVariant}; +use orchid_extension::{ + Atomic, Expr, MethodSetBuilder, Supports, TAtom, ThinAtom, ThinVariant, ToExpr, TryFromExpr, + sys_req, +}; use ordered_float::NotNan; use rust_decimal::prelude::Zero; diff --git a/orchid-std/src/std/number/num_lexer.rs b/orchid-std/src/std/number/num_lexer.rs index b5985f2..756a1e0 100644 --- a/orchid-std/src/std/number/num_lexer.rs +++ b/orchid-std/src/std/number/num_lexer.rs @@ -1,9 +1,8 @@ use std::ops::RangeInclusive; use orchid_base::{OrcRes, num_to_errv, parse_num}; -use orchid_extension::conv::ToExpr; -use orchid_extension::lexer::{LexContext, Lexer}; use orchid_extension::tree::{GenTokTree, x_tok}; +use orchid_extension::{LexContext, Lexer}; use super::num_atom::Num; diff --git a/orchid-std/src/std/number/num_lib.rs b/orchid-std/src/std/number/num_lib.rs index 1775c84..efdc758 100644 --- a/orchid-std/src/std/number/num_lib.rs +++ b/orchid-std/src/std/number/num_lib.rs @@ -1,5 +1,5 @@ use orchid_base::{Numeric, is, mk_errv}; -use orchid_extension::func_atom::get_arg; +use orchid_extension::get_arg; use orchid_extension::tree::{GenMember, fun, prefix}; use ordered_float::NotNan; use rust_decimal::prelude::ToPrimitive; diff --git a/orchid-std/src/std/ops/subscript_lexer.rs b/orchid-std/src/std/ops/subscript_lexer.rs index eb52894..b251b2a 100644 --- a/orchid-std/src/std/ops/subscript_lexer.rs +++ b/orchid-std/src/std/ops/subscript_lexer.rs @@ -1,7 +1,7 @@ use orchid_base::{name_char, name_start}; use orchid_base::{OrcRes, is}; use orchid_extension::gen_expr::new_atom; -use orchid_extension::lexer::{LexContext, LexedData, Lexer, err_not_applicable}; +use orchid_extension::{LexContext, LexedData, Lexer, err_not_applicable}; use orchid_extension::tree::GenTok; use crate::std::string::str_atom::IntStrAtom; diff --git a/orchid-std/src/std/option.rs b/orchid-std/src/std/option.rs index cda3a5a..6931220 100644 --- a/orchid-std/src/std/option.rs +++ b/orchid-std/src/std/option.rs @@ -4,8 +4,8 @@ use std::pin::Pin; use futures::AsyncWrite; use orchid_api_traits::Encode; use orchid_base::{is, mk_errv, sym}; -use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::expr::{Expr, ExprHandle}; +use orchid_extension::{ToExpr, TryFromExpr}; +use orchid_extension::{Expr, ExprHandle}; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; use orchid_extension::{Atomic, DeserializeCtx, ForeignAtom, OwnedAtom, OwnedVariant, TAtom}; diff --git a/orchid-std/src/std/protocol/parse_impls.rs b/orchid-std/src/std/protocol/parse_impls.rs index a948a44..bbb7ae3 100644 --- a/orchid-std/src/std/protocol/parse_impls.rs +++ b/orchid-std/src/std/protocol/parse_impls.rs @@ -5,7 +5,7 @@ use orchid_base::{ }; use orchid_base::{Paren, Token}; use orchid_base::{IStr, OrcRes, is, mk_errv}; -use orchid_extension::parser::{ +use orchid_extension::{ PTokTree, ParsCtx, ParsedLine, ParsedLineKind, p_tree2gen, p_v2gen, }; diff --git a/orchid-std/src/std/protocol/proto_parser.rs b/orchid-std/src/std/protocol/proto_parser.rs index c380c6a..56fdf2a 100644 --- a/orchid-std/src/std/protocol/proto_parser.rs +++ b/orchid-std/src/std/protocol/proto_parser.rs @@ -2,9 +2,9 @@ use std::rc::Rc; use hashbrown::HashMap; use orchid_base::{Comment, OrcRes, Parsed, Token, expect_end, is, mk_errv, sym, try_pop_no_fluff}; -use orchid_extension::conv::ToExpr; +use orchid_extension::ToExpr; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::std::protocol::parse_impls::parse_impls; use crate::std::protocol::types::Tag; diff --git a/orchid-std/src/std/protocol/type_parser.rs b/orchid-std/src/std/protocol/type_parser.rs index a83ee86..52f992c 100644 --- a/orchid-std/src/std/protocol/type_parser.rs +++ b/orchid-std/src/std/protocol/type_parser.rs @@ -2,9 +2,9 @@ use std::rc::Rc; use hashbrown::HashMap; use orchid_base::{Comment, OrcRes, Parsed, Token, expect_end, is, mk_errv, sym, try_pop_no_fluff}; -use orchid_extension::conv::ToExpr; +use orchid_extension::ToExpr; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::std::protocol::parse_impls::parse_impls; use crate::std::protocol::types::Tag; diff --git a/orchid-std/src/std/protocol/types.rs b/orchid-std/src/std/protocol/types.rs index 2044eaf..c35f351 100644 --- a/orchid-std/src/std/protocol/types.rs +++ b/orchid-std/src/std/protocol/types.rs @@ -11,14 +11,11 @@ use never::Never; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::{NameLike, OrcRes, ReqHandleExt, Sym, VName, ev, fmt, is, mk_errv}; -use orchid_extension::conv::{ClonableToExprDyn, ToExpr}; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::expr::{Expr, ExprHandle}; use orchid_extension::gen_expr::{GExpr, call, new_atom}; -use orchid_extension::system::sys_req; use orchid_extension::tree::{GenMember, MemKind, cnst, fun, lazy, prefix}; use orchid_extension::{ - AtomMethod, Atomic, ForeignAtom, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, + AtomMethod, Atomic, ClonableToExprDyn, Expr, ExprHandle, ForeignAtom, MethodSetBuilder, + OwnedAtom, OwnedVariant, Supports, TAtom, ToExpr, exec, sys_req, }; use crate::std::std_system::StdReq; diff --git a/orchid-std/src/std/record/record_atom.rs b/orchid-std/src/std/record/record_atom.rs index acaf0d6..d890535 100644 --- a/orchid-std/src/std/record/record_atom.rs +++ b/orchid-std/src/std/record/record_atom.rs @@ -9,8 +9,8 @@ use hashbrown::HashMap; use orchid_api_derive::Coding; use orchid_api_traits::{Encode, Request}; use orchid_base::{IStr, Receipt, ReqHandle, ReqHandleExt, Sym, es, sym}; -use orchid_extension::conv::ToExpr; -use orchid_extension::expr::Expr; +use orchid_extension::ToExpr; +use orchid_extension::Expr; use orchid_extension::{ Atomic, DeserializeCtx, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, }; diff --git a/orchid-std/src/std/record/record_lib.rs b/orchid-std/src/std/record/record_lib.rs index fa90f3c..a0a1c28 100644 --- a/orchid-std/src/std/record/record_lib.rs +++ b/orchid-std/src/std/record/record_lib.rs @@ -4,7 +4,7 @@ use hashbrown::HashMap; use itertools::Itertools; use orchid_base::{is, mk_errv}; use orchid_extension::TAtom; -use orchid_extension::expr::Expr; +use orchid_extension::Expr; use orchid_extension::gen_expr::{arg, new_atom}; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; diff --git a/orchid-std/src/std/reflection/sym_atom.rs b/orchid-std/src/std/reflection/sym_atom.rs index 57633d6..f4a3782 100644 --- a/orchid-std/src/std/reflection/sym_atom.rs +++ b/orchid-std/src/std/reflection/sym_atom.rs @@ -3,12 +3,11 @@ use std::borrow::Cow; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::{NameLike, ReqHandleExt, Sym, es, is, mk_errv}; -use orchid_extension::{Atomic, Supports, TAtom}; -use orchid_extension::{OwnedAtom, OwnedVariant}; -use orchid_extension::expr::{Expr, ExprHandle}; use orchid_extension::gen_expr::new_atom; -use orchid_extension::system::sys_req; use orchid_extension::tree::{GenMember, fun, prefix}; +use orchid_extension::{ + Atomic, Expr, ExprHandle, OwnedAtom, OwnedVariant, Supports, TAtom, sys_req, +}; use crate::std::std_system::StdReq; use crate::std::string::str_atom::IntStrAtom; diff --git a/orchid-std/src/std/std_system.rs b/orchid-std/src/std/std_system.rs index 7b149aa..68509f0 100644 --- a/orchid-std/src/std/std_system.rs +++ b/orchid-std/src/std/std_system.rs @@ -1,18 +1,14 @@ -use std::cell::OnceCell; use std::rc::Rc; use futures::future::join_all; use orchid_api_derive::{Coding, Hierarchy}; use orchid_base::{Receipt, ReqHandle, ReqHandleExt, Sym, es, sym}; -use orchid_extension::{AtomOps, AtomicFeatures}; -use orchid_extension::conv::ToExpr; -use orchid_extension::expr::Expr; use orchid_extension::gen_expr::new_atom; -use orchid_extension::lexer::LexerObj; -use orchid_extension::parser::ParserObj; -use orchid_extension::system::{System, SystemCard}; -use orchid_extension::system_ctor::SystemCtor; +use orchid_extension::ParserObj; use orchid_extension::tree::{GenMember, merge_trivial}; +use orchid_extension::{ + AtomOps, AtomicFeatures, Expr, LexerObj, ReqForSystem, System, SystemCard, SystemCtor, ToExpr, +}; use super::number::num_lib::gen_num_lib; use super::string::str_atom::{IntStrAtom, StrAtom}; @@ -20,7 +16,6 @@ use super::string::str_lib::gen_str_lib; use crate::std::binary::binary_atom::BlobAtom; use crate::std::binary::binary_lib::gen_binary_lib; use crate::std::boolean::gen_bool_lib; -use crate::std::future::future_lib::{FutureReq, Scheduler, gen_future_lib}; use crate::std::number::num_atom::{CreateFloat, CreateInt}; use crate::std::number::num_lexer::NumLexer; use crate::std::ops::gen_ops_lib; @@ -49,19 +44,17 @@ pub enum StdReq { CreateTuple(CreateTuple), CreateRecord(CreateRecord), CreateSymAtom(CreateSymAtom), - FutureReq(FutureReq), } #[derive(Debug, Default)] -pub struct StdSystem { - pub(crate) sched: OnceCell, -} +pub struct StdSystem; impl SystemCtor for StdSystem { type Deps = (); + type Card = Self; type Instance = Self; const NAME: &'static str = "orchid::std"; const VERSION: f64 = 0.00_01; - fn inst(&self, _: ()) -> Self::Instance { Self::default() } + fn inst(&self, _: ()) -> Self::Instance { Self } } impl SystemCard for StdSystem { type Ctor = Self; @@ -83,16 +76,13 @@ impl SystemCard for StdSystem { } } impl System for StdSystem { - async fn request<'a>(&self, xreq: Box + 'a>, req: Self::Req) -> Receipt<'a> { + type Ctor = Self; + async fn request<'a>( + &self, + xreq: Box + 'a>, + req: ReqForSystem, + ) -> Receipt<'a> { match req { - StdReq::FutureReq(req) => { - let sched = self.sched.get_or_init(Scheduler::default); - match req { - FutureReq::AddAsyncWork(req) => xreq.reply(&req, &sched.add(&req).await).await.unwrap(), - FutureReq::FinishAsyncWork(req) => - xreq.reply(&req, &sched.finish(&req).await).await.unwrap(), - } - }, StdReq::CreateInt(ref req @ CreateInt(int)) => xreq.reply(req, &new_atom(int).to_expr().await.serialize().await).await.unwrap(), StdReq::CreateFloat(ref req @ CreateFloat(float)) => @@ -151,7 +141,6 @@ impl System for StdSystem { gen_binary_lib(), gen_stream_lib(), gen_time_lib(), - gen_future_lib(), ]) } async fn prelude(&self) -> Vec { diff --git a/orchid-std/src/std/stream/stream_cmds.rs b/orchid-std/src/std/stream/stream_cmds.rs index b5a3d9f..adeae3b 100644 --- a/orchid-std/src/std/stream/stream_cmds.rs +++ b/orchid-std/src/std/stream/stream_cmds.rs @@ -1,12 +1,12 @@ use std::borrow::Cow; +use std::io; use std::rc::Rc; use never::Never; -use orchid_base::{OrcRes, fmt, is, mk_errv}; -use orchid_extension::expr::Expr; -use orchid_extension::gen_expr::{GExpr, bot, call, new_atom}; -use orchid_extension::stream_reqs::{ReadLimit, ReadReq}; -use orchid_extension::{Atomic, ForeignAtom, OwnedAtom, OwnedVariant}; +use orchid_base::{ReqHandleExt, fmt, is, mk_errv}; +use orchid_extension::gen_expr::{bot, call, new_atom}; +use orchid_extension::std_reqs::{ReadLimit, ReadReq, RunCommand}; +use orchid_extension::{Atomic, Expr, ForeignAtom, OwnedAtom, OwnedVariant, Supports, ToExpr}; use crate::std::binary::binary_atom::BlobAtom; @@ -24,16 +24,22 @@ impl Atomic for ReadStreamCmd { impl OwnedAtom for ReadStreamCmd { type Refs = Never; async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn command(self) -> OrcRes> { - match self.hand.call(ReadReq(self.limit)).await { +} +impl Supports for ReadStreamCmd { + async fn handle<'a>( + &self, + hand: Box + '_>, + req: RunCommand, + ) -> io::Result> { + let ret = match self.hand.call(ReadReq(self.limit.clone())).await { None => Err(mk_errv( is("Atom is not readable").await, format!("Expected a readable stream handle, found {}", fmt(&self.hand).await), [self.hand.pos()], )), - Some(Err(e)) => Ok(Some( + Some(Err(e)) => Ok( call( - self.fail, + self.fail.clone(), bot(mk_errv( is(e.kind.message()).await, format!("An error occurred while reading: {}", e.message), @@ -41,8 +47,9 @@ impl OwnedAtom for ReadStreamCmd { )), ) .await, - )), - Some(Ok(v)) => Ok(Some(call(self.succ, new_atom(BlobAtom(Rc::new(v)))).await)), - } + ), + Some(Ok(v)) => Ok(call(self.succ.clone(), new_atom(BlobAtom(Rc::new(v)))).await), + }; + hand.reply(&req, &Some(ret.to_gen().await.serialize().await)).await } } diff --git a/orchid-std/src/std/stream/stream_lib.rs b/orchid-std/src/std/stream/stream_lib.rs index d29305d..745af9d 100644 --- a/orchid-std/src/std/stream/stream_lib.rs +++ b/orchid-std/src/std/stream/stream_lib.rs @@ -1,12 +1,14 @@ +use std::num::NonZero; +use std::rc::Rc; + use orchid_base::{is, mk_errv}; -use orchid_extension::ForeignAtom; -use orchid_extension::expr::Expr; -use orchid_extension::func_atom::get_arg; -use orchid_extension::gen_expr::new_atom; -use orchid_extension::stream_reqs::ReadLimit; +use orchid_extension::gen_expr::{call, new_atom}; +use orchid_extension::std_reqs::ReadLimit; use orchid_extension::tree::{GenMember, comments, fun, prefix}; +use orchid_extension::{Expr, ForeignAtom, get_arg}; use crate::Int; +use crate::std::binary::binary_atom::BlobAtom; use crate::std::stream::stream_cmds::ReadStreamCmd; pub fn gen_stream_lib() -> Vec { @@ -30,7 +32,16 @@ pub fn gen_stream_lib() -> Vec { Ok(new_atom(ReadStreamCmd { hand, succ, fail, limit: ReadLimit::Delimiter(end) })) }), fun(true, "read_bytes", async |hand: ForeignAtom, count: Int, succ: Expr, fail: Expr| { - Int(todo!()) + match count.0.try_into().map(NonZero::new) { + Ok(Some(nzlen)) => + Ok(new_atom(ReadStreamCmd { hand, succ, fail, limit: ReadLimit::Length(nzlen) })), + Ok(None) => Ok(call(succ, new_atom(BlobAtom(Rc::default()))).await), + Err(_) => Err(mk_errv( + is("Length cannot be negative").await, + format!("{} is negative and cannot be used as a length", count.0), + [get_arg(1).pos().await], + )), + } }), ]), )]) diff --git a/orchid-std/src/std/string/str_atom.rs b/orchid-std/src/std/string/str_atom.rs index 315508c..c7ad1aa 100644 --- a/orchid-std/src/std/string/str_atom.rs +++ b/orchid-std/src/std/string/str_atom.rs @@ -10,8 +10,8 @@ use orchid_api_traits::{Encode, Request}; use orchid_base::{ FmtCtx, FmtUnit, IStr, OrcRes, Receipt, ReqHandle, ReqHandleExt, Sym, es, is, mk_errv, sym, }; -use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::expr::Expr; +use orchid_extension::{ToExpr, TryFromExpr}; +use orchid_extension::Expr; use orchid_extension::{ AtomMethod, Atomic, DeserializeCtx, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, }; diff --git a/orchid-std/src/std/string/str_lexer.rs b/orchid-std/src/std/string/str_lexer.rs index d3cf925..d3f4fc0 100644 --- a/orchid-std/src/std/string/str_lexer.rs +++ b/orchid-std/src/std/string/str_lexer.rs @@ -1,9 +1,9 @@ use itertools::Itertools; use orchid_base::{OrcErr, OrcErrv, OrcRes, Paren, SrcRange, Sym, is, mk_errv, sym, wrap_tokv}; -use orchid_extension::conv::ToExpr; +use orchid_extension::ToExpr; use orchid_extension::gen_expr::new_atom; -use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; -use orchid_extension::parser::p_tree2gen; +use orchid_extension::{LexContext, Lexer, err_not_applicable}; +use orchid_extension::p_tree2gen; use orchid_extension::tree::{GenTok, GenTokTree, ref_tok, x_tok}; use super::str_atom::IntStrAtom; diff --git a/orchid-std/src/std/string/str_lib.rs b/orchid-std/src/std/string/str_lib.rs index 57582da..4617701 100644 --- a/orchid-std/src/std/string/str_lib.rs +++ b/orchid-std/src/std/string/str_lib.rs @@ -1,12 +1,9 @@ use std::rc::Rc; use orchid_base::{fmt, is, mk_errv, sym}; -use orchid_extension::ForeignAtom; -use orchid_extension::coroutine_exec::exec; -use orchid_extension::expr::Expr; -use orchid_extension::func_atom::get_arg; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, comments, fun, prefix}; +use orchid_extension::{Expr, ForeignAtom, exec, get_arg}; use unicode_segmentation::UnicodeSegmentation; use super::str_atom::StrAtom; diff --git a/orchid-std/src/std/time.rs b/orchid-std/src/std/time.rs index dee0cba..18515ec 100644 --- a/orchid-std/src/std/time.rs +++ b/orchid-std/src/std/time.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::io; use std::time::Instant; use chrono::TimeDelta; @@ -6,13 +7,14 @@ use never::Never; use orchid_api::ExprTicket; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; -use orchid_base::{Numeric, OrcRes}; -use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::expr::Expr; +use orchid_base::{Numeric, OrcRes, Receipt, ReqHandle, ReqHandleExt}; use orchid_extension::gen_expr::{GExpr, call, new_atom}; -use orchid_extension::system::sys_req; +use orchid_extension::std_reqs::{AsDuration, RunCommand}; use orchid_extension::tree::{GenMember, fun, prefix}; -use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom, ThinAtom, ThinVariant}; +use orchid_extension::{ + Atomic, Expr, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, ThinAtom, ThinVariant, + ToExpr, TryFromExpr, sys_req, +}; use ordered_float::NotNan; use crate::std::std_system::StdReq; @@ -42,6 +44,15 @@ impl TryFromExpr for OrcDT { Ok(TAtom::::try_from_expr(expr).await?.value) } } +impl Supports for OrcDT { + async fn handle<'a>( + &self, + hand: Box + '_>, + req: AsDuration, + ) -> std::io::Result> { + hand.reply(&req, &self.0.to_std().unwrap()).await + } +} #[derive(Clone)] pub struct InstantAtom(Instant); @@ -59,12 +70,20 @@ struct Now(Expr); impl Atomic for Now { type Variant = OwnedVariant; type Data = (); + fn reg_methods() -> MethodSetBuilder { MethodSetBuilder::new().handle::() } } impl OwnedAtom for Now { type Refs = Never; async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn command(self) -> OrcRes> { - Ok(Some(call(self.0, new_atom(InstantAtom(Instant::now()))))) +} +impl Supports for Now { + async fn handle<'a>( + &self, + hand: Box + '_>, + req: RunCommand, + ) -> io::Result> { + let cont = call(self.0.clone(), new_atom(InstantAtom(Instant::now()))).await.serialize().await; + hand.reply(&req, &Some(cont)).await } } diff --git a/orchid-std/src/std/tuple.rs b/orchid-std/src/std/tuple.rs index 9458e82..b9381d5 100644 --- a/orchid-std/src/std/tuple.rs +++ b/orchid-std/src/std/tuple.rs @@ -9,13 +9,11 @@ use never::Never; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::{FmtCtx, FmtUnit, Format, OrcRes, ReqHandleExt, Sym, Variants, is, mk_errv, sym}; -use orchid_extension::conv::{ToExpr, TryFromExpr}; -use orchid_extension::expr::{Expr, ExprHandle}; use orchid_extension::gen_expr::{GExpr, new_atom}; -use orchid_extension::system::sys_req; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; use orchid_extension::{ - Atomic, DeserializeCtx, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, + Atomic, DeserializeCtx, Expr, ExprHandle, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, + TAtom, ToExpr, TryFromExpr, sys_req, }; use crate::std::protocol::types::{GetImpl, ProtocolMethod}; @@ -171,9 +169,8 @@ pub struct Tpl(pub T); mod tpl_impls { use itertools::Itertools; use orchid_base::{OrcRes, is, mk_errv}; - use orchid_extension::conv::{ToExpr, TryFromExpr}; - use orchid_extension::expr::Expr; use orchid_extension::gen_expr::GExpr; + use orchid_extension::{Expr, ToExpr, TryFromExpr}; use super::{Tpl, UntypedTuple}; diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 5289a90..8b914cc 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -169,8 +169,8 @@ impl JoinHandle for JoinHandleImpl { struct SpawnerImpl; impl Spawner for SpawnerImpl { - fn spawn_obj(&self, fut: LocalBoxFuture<'static, ()>) -> Box { - Box::new(JoinHandleImpl(spawn_local(fut))) + fn spawn_obj(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) -> Box { + Box::new(JoinHandleImpl(spawn_local(tokio::time::sleep(delay).then(|()| fut)))) } } @@ -317,13 +317,14 @@ async fn main() -> io::Result { let mut xctx = ExecCtx::new(root.clone(), entrypoint).await; eprintln!("executed"); xctx.set_gas(Some(1000)); - xctx.execute().await; - match xctx.result() { - ExecResult::Value(val) => println!( - "{const_name} = {}", - take_first(&val.print(&FmtCtxImpl::default()).await, false) - ), - ExecResult::Err(e) => println!("error: {e}"), + match xctx.execute().await { + ExecResult::Value(val, _) => { + println!( + "{const_name} = {}", + take_first(&val.print(&FmtCtxImpl::default()).await, false) + ) + }, + ExecResult::Err(e, _) => println!("error: {e}"), ExecResult::Gas(_) => println!("Ran out of gas!"), } } @@ -436,12 +437,11 @@ async fn main() -> io::Result { let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos()); let mut xctx = ExecCtx::new(root.clone(), expr).await; xctx.set_gas(Some(10_000)); - xctx.execute().await; - match xctx.result() { - ExecResult::Value(val) => { + match xctx.execute().await { + ExecResult::Value(val, _) => { println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false)) }, - ExecResult::Err(e) => println!("error: {e}"), + ExecResult::Err(e, _) => println!("error: {e}"), ExecResult::Gas(_) => println!("Ran out of gas!"), } },