Files
orchid/orchid-host/src/system.rs

304 lines
10 KiB
Rust

use std::collections::VecDeque;
use std::fmt;
use std::future::Future;
use std::rc::{Rc, Weak};
use derive_destructure::destructure;
use futures::future::join_all;
use futures_locks::RwLock;
use hashbrown::HashMap;
use itertools::Itertools;
use memo_map::MemoMap;
use orchid_base::char_filter::char_filter_match;
use orchid_base::error::{OrcRes, mk_errv_floating};
use orchid_base::format::{FmtCtx, FmtUnit, Format};
use orchid_base::interner::{Interner, Tok};
use orchid_base::iter_utils::IteratorPrint;
use orchid_base::name::{NameLike, Sym, VName, VPath};
use orchid_base::reqnot::{ReqNot, Requester};
use ordered_float::NotNan;
use substack::{Stackframe, Substack};
use crate::api;
use crate::atom::{AtomHand, WeakAtomHand};
use crate::ctx::Ctx;
use crate::dealias::walk;
use crate::extension::{Extension, WeakExtension};
use crate::sys_parser::Parser;
use crate::tree::Root;
#[derive(destructure)]
pub(crate) struct SystemInstData {
deps: Vec<System>,
ctx: Ctx,
ext: Extension,
decl_id: api::SysDeclId,
lex_filter: api::CharFilter,
id: api::SysId,
line_types: Vec<Tok<String>>,
prelude: Vec<Sym>,
owned_atoms: RwLock<HashMap<api::AtomId, WeakAtomHand>>,
pub(crate) const_paths: MemoMap<api::ParsedConstId, Sym>,
}
impl Drop for SystemInstData {
fn drop(&mut self) { self.ext.system_drop(self.id); }
}
impl fmt::Debug for SystemInstData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemInstData")
.field("decl_id", &self.decl_id)
.field("lex_filter", &self.lex_filter)
.field("id", &self.id)
.field("line_types", &self.line_types)
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug)]
pub struct System(pub(crate) Rc<SystemInstData>);
impl System {
#[must_use]
pub async fn atoms(&self) -> impl std::ops::Deref<Target = HashMap<api::AtomId, WeakAtomHand>> {
self.0.owned_atoms.read().await
}
#[must_use]
pub fn id(&self) -> api::SysId { self.0.id }
#[must_use]
pub fn ext(&self) -> &Extension { &self.0.ext }
#[must_use]
pub fn ctx(&self) -> &Ctx { &self.0.ctx }
#[must_use]
pub fn i(&self) -> &Interner { &self.0.ctx.i }
#[must_use]
pub fn deps(&self) -> &[System] { &self.0.deps }
#[must_use]
pub fn ctor(&self) -> SystemCtor {
(self.0.ext.system_ctors().find(|c| c.decl.id == self.0.decl_id).cloned())
.expect("Ctor was used to create ext")
}
#[must_use]
pub(crate) fn reqnot(&self) -> &ReqNot<api::HostMsgSet> { self.0.ext.reqnot() }
#[must_use]
pub async fn get_tree(&self, id: api::TreeId) -> api::MemberKind {
self.reqnot().request(api::GetMember(self.0.id, id)).await
}
#[must_use]
pub fn has_lexer(&self) -> bool { !self.0.lex_filter.0.is_empty() }
#[must_use]
pub fn can_lex(&self, c: char) -> bool { char_filter_match(&self.0.lex_filter, c) }
#[must_use]
pub fn prelude(&self) -> Vec<Sym> { self.0.prelude.clone() }
/// Have this system lex a part of the source. It is assumed that
/// [Self::can_lex] was called and returned true.
pub async fn lex<F: Future<Output = Option<api::SubLexed>>>(
&self,
source: Tok<String>,
src: Sym,
pos: u32,
r: impl FnMut(u32) -> F,
) -> api::OrcResult<Option<api::LexedExpr>> {
self.0.ext.lex_req(source, src, pos, self.id(), r).await
}
#[must_use]
pub fn get_parser(&self, ltyp: Tok<String>) -> Option<Parser> {
(self.0.line_types.iter().enumerate())
.find(|(_, txt)| *txt == &ltyp)
.map(|(idx, _)| Parser { idx: idx as u16, system: self.clone() })
}
pub fn line_types(&self) -> impl Iterator<Item = &Tok<String>> + '_ { self.0.line_types.iter() }
#[must_use]
pub async fn request(&self, req: Vec<u8>) -> Vec<u8> {
self.reqnot().request(api::SysFwded(self.id(), req)).await
}
pub(crate) async fn new_atom(&self, data: Vec<u8>, id: api::AtomId) -> AtomHand {
let mut owned_g = self.0.owned_atoms.write().await;
if let Some(data) = owned_g.get(&id)
&& let Some(atom) = data.upgrade()
{
return atom;
}
let new = AtomHand::new(data, self.clone(), Some(id));
owned_g.insert(id, new.downgrade());
new
}
pub(crate) fn drop_atom(&self, dropped_atom_id: api::AtomId) {
let this = self.0.clone();
(self.0.ctx.spawn)(Box::pin(async move {
this.ext.reqnot().request(api::AtomDrop(this.id, dropped_atom_id)).await;
this.owned_atoms.write().await.remove(&dropped_atom_id);
}))
}
#[must_use]
pub fn downgrade(&self) -> WeakSystem {
WeakSystem(Rc::downgrade(&self.0), self.0.decl_id, self.ext().downgrade())
}
/// Implementation of [api::ResolveNames]
pub(crate) async fn name_resolver(
&self,
orig: api::ParsedConstId,
) -> impl AsyncFnMut(&[Tok<String>]) -> OrcRes<VName> + use<> {
let root = self.0.ctx.root.read().await.upgrade().expect("find_names when root not in context");
let orig = self.0.const_paths.get(&orig).expect("origin for find_names invalid").clone();
let ctx = self.0.ctx.clone();
async move |rel| {
let cwd = orig.split_last_seg().1;
let root_data = &mut *root.0.write().await;
let walk_ctx = &mut (ctx.clone(), &root_data.consts);
let cmod = (walk(&root_data.root, false, cwd.iter().cloned(), walk_ctx).await)
.expect("the parent module of a constant should exist");
let (selector, tail) = rel.split_first().expect("Names cannot be empty");
if cmod.members.get(selector).is_some() {
return Ok(VName::new(cwd.iter().chain(rel).cloned()).unwrap());
}
match cmod.imports.get(selector) {
Some(Ok(dest)) => return Ok(dest.target.to_vname().suffix(tail.iter().cloned())),
Some(Err(dests)) =>
return Err(mk_errv_floating(
ctx.i.i("Ambiguous name").await,
format!(
"{selector} could refer to {}",
dests.iter().map(|ri| &ri.target).display("or")
),
)),
None => (),
}
if root_data.root.members.get(selector).is_some() {
return Ok(VName::new(rel.iter().cloned()).expect("split_first was called above"));
}
if tail.is_empty() {
return Ok(VPath::new(cwd.iter().cloned()).name_with_suffix(selector.clone()));
}
Err(mk_errv_floating(
ctx.i.i("Invalid name").await,
format!("{selector} doesn't refer to a module"),
))
}
}
}
impl Format for System {
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
let ctor = (self.0.ext.system_ctors().find(|c| c.id() == self.0.decl_id))
.expect("System instance with no associated constructor");
format!("System({} @ {} #{})", ctor.name(), ctor.priority(), self.0.id.0).into()
}
}
pub struct WeakSystem(Weak<SystemInstData>, api::SysDeclId, WeakExtension);
impl WeakSystem {
#[must_use]
pub fn upgrade(&self) -> Option<System> { self.0.upgrade().map(System) }
pub fn ext(&self) -> Option<Extension> { self.2.upgrade() }
pub fn ctor(&self) -> Option<SystemCtor> {
self.ext()?.system_ctors().find(|ctor| ctor.decl.id == self.1).cloned()
}
}
#[derive(Clone)]
pub struct SystemCtor {
pub(crate) decl: api::SystemDecl,
pub(crate) ext: WeakExtension,
}
impl SystemCtor {
#[must_use]
pub fn name(&self) -> &str { &self.decl.name }
pub async fn name_tok(&self) -> Sym {
(Sym::parse(&self.decl.name, &self.ext.upgrade().expect("ext dropped early").ctx().i).await)
.expect("System cannot have empty name")
}
#[must_use]
pub fn priority(&self) -> NotNan<f64> { self.decl.priority }
#[must_use]
pub fn depends(&self) -> impl ExactSizeIterator<Item = &str> {
self.decl.depends.iter().map(|s| &**s)
}
#[must_use]
pub fn id(&self) -> api::SysDeclId { self.decl.id }
#[must_use]
pub async fn run(&self, deps: Vec<System>) -> (Root, System) {
let depends = deps.iter().map(|si| si.id()).collect_vec();
debug_assert_eq!(depends.len(), self.decl.depends.len(), "Wrong number of deps provided");
let ext = self.ext.upgrade().expect("SystemCtor should be freed before Extension");
let id = ext.ctx().next_sys_id();
let sys_inst = ext.reqnot().request(api::NewSystem { depends, id, system: self.decl.id }).await;
let data = System(Rc::new(SystemInstData {
deps,
decl_id: self.decl.id,
ext: ext.clone(),
ctx: ext.ctx().clone(),
lex_filter: sys_inst.lex_filter,
line_types: join_all(sys_inst.line_types.iter().map(|m| Tok::from_api(*m, &ext.ctx().i)))
.await,
id,
prelude: join_all(sys_inst.prelude.iter().map(|tok| Sym::from_api(*tok, &ext.ctx().i))).await,
owned_atoms: RwLock::new(HashMap::new()),
const_paths: MemoMap::new(),
}));
let api_module_root = api::Module {
members: (sys_inst.const_root.into_iter())
.map(|(k, v)| api::Member { name: k, kind: v, comments: vec![], exported: true })
.collect_vec(),
};
let root = Root::from_api(api_module_root, &data).await;
ext.ctx().systems.write().await.insert(id, data.downgrade());
(root, data)
}
}
#[derive(Debug, Clone)]
pub enum SysResolvErr {
Loop(Vec<String>),
Missing(String),
}
pub async fn init_systems(
tgts: &[String],
exts: &[Extension],
) -> Result<(Root, Vec<System>), SysResolvErr> {
let mut to_load = HashMap::<&str, &SystemCtor>::new();
let mut to_find = tgts.iter().map(|s| s.as_str()).collect::<VecDeque<&str>>();
while let Some(target) = to_find.pop_front() {
if to_load.contains_key(target) {
continue;
}
let ctor = (exts.iter())
.flat_map(|e| e.system_ctors().filter(|c| c.name() == target))
.max_by_key(|c| c.priority())
.ok_or_else(|| SysResolvErr::Missing(target.to_string()))?;
to_load.insert(target, ctor);
to_find.extend(ctor.depends());
}
let mut to_load_ordered = Vec::new();
fn walk_deps<'a>(
graph: &mut HashMap<&str, &'a SystemCtor>,
list: &mut Vec<&'a SystemCtor>,
chain: Stackframe<&str>,
) -> Result<(), SysResolvErr> {
if let Some(ctor) = graph.remove(chain.item) {
// if the above is none, the system is already queued. Missing systems are
// detected above
for dep in ctor.depends() {
if Substack::Frame(chain).iter().any(|c| *c == dep) {
let mut circle = vec![dep.to_string()];
circle.extend(Substack::Frame(chain).iter().map(|s| s.to_string()));
return Err(SysResolvErr::Loop(circle));
}
walk_deps(graph, list, Substack::Frame(chain).new_frame(dep))?
}
list.push(ctor);
}
Ok(())
}
for tgt in tgts {
walk_deps(&mut to_load, &mut to_load_ordered, Substack::Bottom.new_frame(tgt))?;
}
let mut systems = HashMap::<&str, System>::new();
let mut root = Root::new(exts.first().unwrap().ctx().clone());
for ctor in to_load_ordered.iter() {
let (sys_root, sys) = ctor.run(ctor.depends().map(|n| systems[n].clone()).collect()).await;
systems.insert(ctor.name(), sys);
root = root.merge(&sys_root).await.expect("Conflicting roots");
}
Ok((root, systems.into_values().collect_vec()))
}