use std::any::{Any, TypeId}; use std::fmt::Debug; use std::future::Future; use std::num::NonZero; use std::pin::Pin; use futures::FutureExt; use futures::future::LocalBoxFuture; use orchid_api_traits::{Coding, Decode, Encode, Request}; use orchid_base::boxed_iter::BoxedIter; use orchid_base::name::Sym; use orchid_base::reqnot::{Receipt, ReqHandle, ReqReader, ReqReaderExt}; use task_local::task_local; use crate::api; use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TAtom, get_info}; 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; /// System as consumed by foreign code pub trait SystemCard: Debug + Default + Send + Sync + 'static { type Ctor: SystemCtor; type Req: Coding; fn atoms() -> impl IntoIterator>>; } pub trait DynSystemCard: Send + Sync + 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>>; } /// 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::dynfo()), Some(Lambda::dynfo()), Some(Replier::dynfo())].into_iter() } pub fn atom_info_for( sys: &(impl DynSystemCard + ?Sized), tid: TypeId, ) -> Option<(AtomTypeId, Box)> { (sys.atoms().enumerate().map(|(i, o)| (NonZero::new(i as u32 + 1).unwrap(), o))) .chain(general_atoms().enumerate().map(|(i, o)| (NonZero::new(!(i as u32)).unwrap(), o))) .filter_map(|(i, o)| o.map(|a| (AtomTypeId(i), a))) .find(|ent| ent.1.tid() == tid) } pub fn atom_by_idx( sys: &(impl DynSystemCard + ?Sized), tid: AtomTypeId, ) -> Option> { if (u32::from(tid.0) >> (u32::BITS - 1)) & 1 == 1 { general_atoms().nth(!u32::from(tid.0) as usize).unwrap() } else { sys.atoms().nth(u32::from(tid.0) as usize - 1).unwrap() } } pub async fn resolv_atom( sys: &(impl DynSystemCard + ?Sized), atom: &api::Atom, ) -> Box { let tid = AtomTypeId::decode(Pin::new(&mut &atom.data.0[..])).await.unwrap(); atom_by_idx(sys, tid).expect("Value of nonexistent type found") } impl DynSystemCard for T { fn name(&self) -> &'static str { T::Ctor::NAME } fn atoms(&'_ self) -> BoxedIter<'_, Option>> { Box::new(Self::atoms().into_iter()) } } /// System as defined by author pub trait System: Send + Sync + SystemCard + 'static { fn prelude() -> impl Future>; fn env() -> impl Future>; fn lexers() -> Vec; fn parsers() -> Vec; fn request<'a>( hand: Box + 'a>, req: Self::Req, ) -> impl Future>; } pub trait DynSystem: Send + Sync + DynSystemCard + 'static { fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec>; fn dyn_env(&self) -> LocalBoxFuture<'_, Vec>; fn dyn_lexers(&self) -> Vec; fn dyn_parsers(&self) -> Vec; fn dyn_request<'a>(&self, hand: Box + 'a>) -> LocalBoxFuture<'a, Receipt<'a>>; fn card(&self) -> &dyn DynSystemCard; } impl DynSystem for T { fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec> { Box::pin(Self::prelude()) } fn dyn_env(&self) -> LocalBoxFuture<'_, Vec> { Self::env().boxed_local() } fn dyn_lexers(&self) -> Vec { Self::lexers() } fn dyn_parsers(&self) -> Vec { Self::parsers() } fn dyn_request<'a>( &self, mut hand: Box + 'a>, ) -> LocalBoxFuture<'a, Receipt<'a>> { Box::pin(async move { let value = hand.read_req::<::Req>().await.unwrap(); Self::request(hand.finish().await, value).await }) } fn card(&self) -> &dyn DynSystemCard { self } } #[derive(Clone)] pub(crate) struct SysCtx(pub api::SysId, pub CtedObj); task_local! { static SYS_CTX: SysCtx; } pub(crate) async fn with_sys(sys: SysCtx, fut: F) -> F::Output { SYS_CTX.scope(sys, fut).await } pub fn sys_id() -> api::SysId { SYS_CTX.with(|cx| cx.0) } pub fn cted() -> CtedObj { SYS_CTX.with(|cx| cx.1.clone()) } pub async fn downcast_atom(foreign: ForeignAtom) -> Result, ForeignAtom> where A: AtomicFeatures { let mut data = &foreign.atom.data.0[..]; let value = AtomTypeId::decode_slice(&mut data); let cted = cted(); let own_inst = cted.inst(); let owner = if sys_id() == foreign.atom.owner { own_inst.card() } else { cted.deps().find(|s| s.id() == foreign.atom.owner).ok_or_else(|| foreign.clone())?.get_card() }; if owner.atoms().flatten().all(|dynfo| dynfo.tid() != TypeId::of::()) { return Err(foreign); } let (typ_id, dynfo) = get_info::(owner); if value != typ_id { return Err(foreign); } let val = dynfo.decode(AtomCtx(data, foreign.atom.drop)).await; let Ok(value) = val.downcast::() else { panic!("decode of {} returned wrong type.", dynfo.name()); }; Ok(TAtom { value: *value, untyped: foreign }) } pub async fn dep_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() }