use std::fmt; use std::rc::Rc; use async_once_cell::OnceCell; use derive_destructure::destructure; use orchid_api::ExtAtomPrint; use orchid_base::error::OrcErrv; use orchid_base::format::{FmtCtx, FmtUnit, Format}; use orchid_base::location::Pos; use orchid_base::reqnot::Requester; use crate::api; use crate::atom::ForeignAtom; use crate::gen_expr::{GExpr, GExprKind}; use crate::system::SysCtx; #[derive(destructure)] pub struct ExprHandle { pub tk: api::ExprTicket, pub ctx: SysCtx, } impl ExprHandle { /// # Safety /// /// This function does not signal to take ownership of the expr. It must only /// be called on tickets that are already implicitly owned. pub unsafe fn from_args(ctx: SysCtx, tk: api::ExprTicket) -> Self { Self { ctx, tk } } pub fn get_ctx(&self) -> SysCtx { self.ctx.clone() } pub async fn clone(&self) -> Self { self.ctx.reqnot().notify(api::Acquire(self.ctx.sys_id(), self.tk)).await; Self { ctx: self.ctx.clone(), tk: self.tk } } /// Drop the handle and get the ticket without a release notification. /// Use this with messages that imply ownership transfer. This function is /// safe because abusing it is a memory leak. pub fn into_tk(self) -> api::ExprTicket { self.destructure().0 } } impl fmt::Debug for ExprHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ExprHandle({})", self.tk.0) } } impl Drop for ExprHandle { fn drop(&mut self) { let notif = api::Release(self.ctx.sys_id(), self.tk); let reqnot = self.ctx.reqnot().clone(); self.ctx.spawner()(Box::pin(async move { reqnot.notify(notif).await })) } } #[derive(Clone, Debug, destructure)] pub struct Expr { handle: Rc, data: Rc>, } impl Expr { pub fn from_handle(handle: Rc) -> Self { Self { handle, data: Rc::default() } } pub fn new(handle: Rc, d: ExprData) -> Self { Self { handle, data: Rc::new(OnceCell::from(d)) } } pub async fn data(&self) -> &ExprData { (self.data.get_or_init(async { let details = self.handle.ctx.reqnot().request(api::Inspect { target: self.handle.tk }).await; let pos = Pos::from_api(&details.location, self.handle.ctx.i()).await; let kind = match details.kind { api::InspectedKind::Atom(a) => ExprKind::Atom(ForeignAtom::new(self.handle.clone(), a, pos.clone())), api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b, self.handle.ctx.i()).await), api::InspectedKind::Opaque => ExprKind::Opaque, }; ExprData { pos, kind } })) .await } pub async fn atom(self) -> Result { match self.data().await { ExprData { kind: ExprKind::Atom(atom), .. } => Ok(atom.clone()), _ => Err(self), } } pub fn handle(&self) -> Rc { self.handle.clone() } pub fn ctx(&self) -> SysCtx { self.handle.ctx.clone() } pub fn slot(&self) -> GExpr { GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) } } } impl Format for Expr { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match &self.data().await.kind { ExprKind::Opaque => "OPAQUE".to_string().into(), ExprKind::Bottom(b) => format!("Bottom({b})").into(), ExprKind::Atom(a) => FmtUnit::from_api(&self.handle.ctx.reqnot().request(ExtAtomPrint(a.atom.clone())).await), } } } #[derive(Clone, Debug)] pub struct ExprData { pub pos: Pos, pub kind: ExprKind, } #[derive(Clone, Debug)] pub enum ExprKind { Atom(ForeignAtom), Bottom(OrcErrv), Opaque, }