forked from Orchid/orchid
304 lines
10 KiB
Rust
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 == <yp)
|
|
.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()))
|
|
}
|