Traditional route appears to work

Beginnings of dylib extensions, entirely untestted
This commit is contained in:
2026-01-12 01:38:10 +01:00
parent 32d6237dc5
commit 1a7230ce9b
40 changed files with 1560 additions and 1135 deletions

View File

@@ -14,11 +14,10 @@ use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, SinkExt, StreamExt};
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use orchid_api_traits::{Decode, Encode, Request};
use orchid_base::clone;
use orchid_base::format::{FmtCtxImpl, Format};
use orchid_base::interner::{IStr, IStrv, es, ev, is, iv};
use orchid_base::location::Pos;
use orchid_base::logging::logger;
use orchid_base::logging::log;
use orchid_base::name::Sym;
use orchid_base::reqnot::{Client, ClientExt, MsgReaderExt, ReqHandleExt, ReqReaderExt, io_comm};
use orchid_base::stash::{stash, with_stash};
@@ -70,199 +69,213 @@ impl Drop for ExtensionData {
pub struct Extension(Rc<ExtensionData>);
impl Extension {
pub async fn new(mut init: ExtPort, ctx: Ctx) -> io::Result<Self> {
api::HostHeader { log_strategy: logger().strat(), msg_logs: ctx.msg_logs.strat() }
.encode(init.input.as_mut())
.await
.unwrap();
api::HostHeader { logger: ctx.logger.to_api() }.encode(init.input.as_mut()).await.unwrap();
init.input.flush().await.unwrap();
let header = api::ExtensionHeader::decode(init.output.as_mut()).await.unwrap();
let header2 = header.clone();
Ok(Self(Rc::new_cyclic(|weak: &Weak<ExtensionData>| {
// context not needed because exit is extension-initiated
let (client, _, future) = io_comm(
Rc::new(Mutex::new(init.input)),
Mutex::new(init.output),
clone!(weak; async move |reader| {
with_stash(async {
let this = Extension(weak.upgrade().unwrap());
let notif = reader.read::<api::ExtHostNotif>().await.unwrap();
if !matches!(notif, api::ExtHostNotif::Log(_)) {
writeln!(logger(), "Host received notif {notif:?}");
}
match notif {
api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => {
let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket");
this.0.ctx.exprs.give_expr(target)
}
api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => {
if this.is_own_sys(rel.0).await {
this.0.ctx.exprs.take_expr(rel.1);
} else {
writeln!(this.0.ctx.msg_logs, "Not our system {:?}", rel.0)
let (client, _, comm) = io_comm(Rc::new(Mutex::new(init.input)), Mutex::new(init.output));
let weak2 = weak;
let weak = weak.clone();
let ctx2 = ctx.clone();
let join_ext = ctx.clone().spawn(async move {
comm
.listen(
async |reader| {
with_stash(async {
let this = Extension(weak.upgrade().unwrap());
let notif = reader.read::<api::ExtHostNotif>().await.unwrap();
// logging is never logged because its value will be logged anyway
if !matches!(notif, api::ExtHostNotif::Log(_)) {
writeln!(log("msg"), "Host received notif {notif:?}").await;
}
},
api::ExtHostNotif::Log(api::Log(str)) => logger().log(str),
api::ExtHostNotif::Sweeped(data) => {
for i in join_all(data.strings.into_iter().map(es)).await {
this.0.strings.borrow_mut().remove(&i);
}
for i in join_all(data.vecs.into_iter().map(ev)).await {
this.0.string_vecs.borrow_mut().remove(&i);
}
},
}
Ok(())
}).await
}),
{
clone!(weak, ctx);
async move |mut reader| {
with_stash(async {
let this = Self(weak.upgrade().unwrap());
let req = reader.read_req::<api::ExtHostReq>().await.unwrap();
let handle = reader.finish().await;
if !matches!(req, api::ExtHostReq::ExtAtomPrint(_)) {
writeln!(logger(), "Host received request {req:?}");
}
match req {
api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await,
api::ExtHostReq::IntReq(intreq) => match intreq {
api::IntReq::InternStr(s) => {
let i = is(&s.0).await;
this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&s, &i.to_api()).await
match notif {
api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => {
let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket");
this.0.ctx.exprs.give_expr(target)
},
api::IntReq::InternStrv(v) => {
let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
this.0.strings.borrow_mut().extend(tokens.iter().cloned());
let i = iv(&tokens).await;
this.0.string_vecs.borrow_mut().insert(i.clone());
handle.reply(&v, &i.to_api()).await
},
api::IntReq::ExternStr(si) => {
let i = es(si.0).await;
this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&si, &i.to_string()).await
},
api::IntReq::ExternStrv(vi) => {
let i = ev(vi.0).await;
this.0.strings.borrow_mut().extend(i.iter().cloned());
this.0.string_vecs.borrow_mut().insert(i.clone());
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
handle.reply(&vi, &markerv).await
},
},
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
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();
handle.reply(fw, &reply).await
},
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
let sys = ctx.system_inst(id).await.unwrap();
handle.reply(fw, &sys.request(body.clone()).await).await
},
api::ExtHostReq::SubLex(sl) => {
let (rep_in, mut rep_out) = channel(0);
{
let lex_g = this.0.lex_recur.lock().await;
let mut req_in =
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
}
handle.reply(&sl, &rep_out.next().await.unwrap()).await
},
api::ExtHostReq::ExprReq(expr_req) => match expr_req {
api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
handle
.reply(&ins, &api::Inspected {
refcount: expr.strong_count() as u32,
location: expr.pos().to_api(),
kind: expr.to_api().await,
})
.await
},
api::ExprReq::Create(ref cre @ api::Create(ref expr)) => {
let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await;
let expr_id = expr.id();
ctx.exprs.give_expr(expr);
handle.reply(cre, &expr_id).await
},
},
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
let reply: <api::LsModule as Request>::Response = 'reply: {
let path = ev(path).await;
let root = (ctx.root.read().await.upgrade())
.expect("LSModule called when root isn't in context");
let root_data = &*root.0.read().await;
let mut walk_ctx = (ctx.clone(), &root_data.consts);
let module =
match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx).await
{
Ok(module) => module,
Err(ChildError { kind, .. }) =>
break 'reply Err(match kind {
ChildErrorKind::Private => panic!("Access checking was disabled"),
ChildErrorKind::Constant => api::LsModuleError::IsConstant,
ChildErrorKind::Missing => api::LsModuleError::InvalidPath,
}),
};
let mut members = std::collections::HashMap::new();
for (k, v) in &module.members {
let kind = match v.kind(ctx.clone(), &root_data.consts).await {
MemberKind::Const => api::MemberInfoKind::Constant,
MemberKind::Module(_) => api::MemberInfoKind::Module,
};
members.insert(k.to_api(), api::MemberInfo { public: v.public, kind });
api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => {
if this.is_own_sys(rel.0).await {
this.0.ctx.exprs.take_expr(rel.1);
} else {
writeln!(log("warn"), "Not our system {:?}", rel.0).await
}
Ok(api::ModuleInfo { members })
};
handle.reply(ls, &reply).await
},
api::ExtHostReq::ResolveNames(ref rn) => {
let api::ResolveNames { constid, names, sys } = rn;
let mut resolver = {
let systems = ctx.systems.read().await;
let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys");
let sys = weak_sys.upgrade().expect("ResolveNames after sys drop");
sys.name_resolver(*constid).await
};
let responses = stream(async |mut cx| {
for name in names {
cx.emit(match resolver(&ev(*name).await[..]).await {
Ok(abs) => Ok(abs.to_sym().await.to_api()),
Err(e) => Err(e.to_api()),
})
.await
},
api::ExtHostNotif::Log(api::Log { category, message }) =>
write!(log(&es(category).await), "{message}").await,
api::ExtHostNotif::Sweeped(data) => {
for i in join_all(data.strings.into_iter().map(es)).await {
this.0.strings.borrow_mut().remove(&i);
}
})
.collect()
.await;
handle.reply(rn, &responses).await
},
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
let unit = atom.print(&FmtCtxImpl::default()).await;
handle.reply(eap, &unit.to_api()).await
},
}
})
.await
}
},
);
let join_ext = ctx.spawn(async {
future.await.unwrap();
// extension exited successfully
for i in join_all(data.vecs.into_iter().map(ev)).await {
this.0.string_vecs.borrow_mut().remove(&i);
}
},
}
Ok(())
})
.await
},
async |mut reader| {
with_stash(async {
let req = reader.read_req::<api::ExtHostReq>().await.unwrap();
let handle = reader.finish().await;
// Atom printing and interning is never reported because it generates too much
// noise
if !matches!(req, api::ExtHostReq::ExtAtomPrint(_))
|| matches!(req, api::ExtHostReq::IntReq(_))
{
writeln!(log("msg"), "Host received request {req:?}").await;
}
let this = Self(weak.upgrade().unwrap());
match req {
api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await,
api::ExtHostReq::IntReq(intreq) => match intreq {
api::IntReq::InternStr(s) => {
let i = is(&s.0).await;
this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&s, &i.to_api()).await
},
api::IntReq::InternStrv(v) => {
let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
this.0.strings.borrow_mut().extend(tokens.iter().cloned());
let i = iv(&tokens).await;
this.0.string_vecs.borrow_mut().insert(i.clone());
handle.reply(&v, &i.to_api()).await
},
api::IntReq::ExternStr(si) => {
let i = es(si.0).await;
this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&si, &i.to_string()).await
},
api::IntReq::ExternStrv(vi) => {
let i = ev(vi.0).await;
this.0.strings.borrow_mut().extend(i.iter().cloned());
this.0.string_vecs.borrow_mut().insert(i.clone());
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
handle.reply(&vi, &markerv).await
},
},
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
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();
handle.reply(fw, &reply).await
},
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
let sys = ctx.system_inst(id).await.unwrap();
handle.reply(fw, &sys.request(body.clone()).await).await
},
api::ExtHostReq::SubLex(sl) => {
let (rep_in, mut rep_out) = channel(0);
{
let lex_g = this.0.lex_recur.lock().await;
let mut req_in =
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
}
handle.reply(&sl, &rep_out.next().await.unwrap()).await
},
api::ExtHostReq::ExprReq(expr_req) => match expr_req {
api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
handle
.reply(&ins, &api::Inspected {
refcount: expr.strong_count() as u32,
location: expr.pos().to_api(),
kind: expr.to_api().await,
})
.await
},
api::ExprReq::Create(ref cre @ api::Create(ref expr)) => {
let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await;
let expr_id = expr.id();
ctx.exprs.give_expr(expr);
handle.reply(cre, &expr_id).await
},
},
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
let reply: <api::LsModule as Request>::Response = 'reply: {
let path = ev(path).await;
let root = (ctx.root.read().await.upgrade())
.expect("LSModule called when root isn't in context");
let root_data = &*root.0.read().await;
let mut walk_ctx = (ctx.clone(), &root_data.consts);
let module =
match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx)
.await
{
Ok(module) => module,
Err(ChildError { kind, .. }) =>
break 'reply Err(match kind {
ChildErrorKind::Private => panic!("Access checking was disabled"),
ChildErrorKind::Constant => api::LsModuleError::IsConstant,
ChildErrorKind::Missing => api::LsModuleError::InvalidPath,
}),
};
let mut members = std::collections::HashMap::new();
for (k, v) in &module.members {
let kind = match v.kind(ctx.clone(), &root_data.consts).await {
MemberKind::Const => api::MemberInfoKind::Constant,
MemberKind::Module(_) => api::MemberInfoKind::Module,
};
members.insert(k.to_api(), api::MemberInfo { public: v.public, kind });
}
Ok(api::ModuleInfo { members })
};
handle.reply(ls, &reply).await
},
api::ExtHostReq::ResolveNames(ref rn) => {
let api::ResolveNames { constid, names, sys } = rn;
let mut resolver = {
let systems = ctx.systems.read().await;
let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys");
let sys = weak_sys.upgrade().expect("ResolveNames after sys drop");
sys.name_resolver(*constid).await
};
let responses = stream(async |mut cx| {
for name in names {
cx.emit(match resolver(&ev(*name).await[..]).await {
Ok(abs) => {
let sym = abs.to_sym().await;
this.0.string_vecs.borrow_mut().insert(sym.tok());
Ok(sym.to_api())
},
Err(e) => {
(this.0.strings.borrow_mut())
.extend(e.iter().map(|e| e.description.clone()));
Err(e.to_api())
},
})
.await
}
})
.collect()
.await;
handle.reply(rn, &responses).await
},
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
let unit = atom.print(&FmtCtxImpl::default()).await;
handle.reply(eap, &unit.to_api()).await
},
}
})
.await
},
)
.await
.unwrap();
});
ExtensionData {
name: header.name.clone(),
ctx: ctx.clone(),
name: header2.name.clone(),
ctx: ctx2,
systems: (header.systems.iter().cloned())
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) })
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak2.clone()) })
.collect(),
join_ext: Some(join_ext),
next_pars: RefCell::new(NonZeroU64::new(1).unwrap()),
@@ -282,7 +295,7 @@ impl Extension {
#[must_use]
pub async fn is_own_sys(&self, id: api::SysId) -> bool {
let Some(sys) = self.ctx().system_inst(id).await else {
writeln!(logger(), "Invalid system ID {id:?}");
writeln!(log("warn"), "Invalid system ID {id:?}").await;
return false;
};
Rc::ptr_eq(&self.0, &sys.ext().0)