use std::{
cmp::Ordering,
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
ops::Deref,
sync::{RwLock, RwLockReadGuard},
};
use ahash::{AHashMap, AHashSet, RandomState};
use lady_deirdre::{
arena::Id,
sync::{Lazy, Table},
};
use semver::{Version, VersionReq};
use crate::{
report::debug_unreachable,
runtime::{
Cell,
RustOrigin,
TypeMeta,
__intrinsics::{DeclarationGroup, PackageDeclaration},
},
};
pub trait ScriptPackage {
#[inline(always)]
fn origin() -> &'static RustOrigin {
Self::meta().origin()
}
#[inline(always)]
fn name() -> &'static str {
Self::meta().name()
}
#[inline(always)]
fn version() -> &'static str {
Self::meta().version()
}
fn meta() -> &'static PackageMeta;
}
pub struct PackageMeta {
origin: &'static RustOrigin,
declaration: PackageDeclaration,
modules: RwLock<AHashSet<Id>>,
}
impl PartialEq for PackageMeta {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.declaration.name.ne(other.declaration.name) {
return false;
}
if self.declaration.version.ne(other.declaration.version) {
return false;
}
true
}
}
impl Eq for PackageMeta {}
impl Hash for PackageMeta {
fn hash<H: Hasher>(&self, state: &mut H) {
self.declaration.name.hash(state);
self.declaration.version.hash(state);
}
}
impl Ord for PackageMeta {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
match self.declaration.name.cmp(other.declaration.name) {
Ordering::Equal => self.declaration.version.cmp(other.declaration.version),
other => other,
}
}
}
impl PartialOrd for PackageMeta {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Debug for PackageMeta {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let alternate = formatter.alternate();
let mut debug_struct = formatter.debug_struct("PackageMeta");
if alternate {
debug_struct.field("origin", self.origin());
}
debug_struct
.field("name", &self.name())
.field("version", &self.version());
if alternate {
if let Ok(modules) = self.modules.try_read() {
struct ListModules<'a> {
modules: RwLockReadGuard<'a, AHashSet<Id>>,
}
impl<'a> Debug for ListModules<'a> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let mut list = formatter.debug_list();
for id in self.modules.iter() {
let name = id.name();
match name.is_empty() {
true => list.entry(&format_args!("‹#{}›", id.into_inner())),
false => list.entry(&format_args!("‹{}›", name)),
};
}
list.finish()
}
}
let print_modules = ListModules { modules };
debug_struct.field("modules", &print_modules);
}
let prototype = self.declaration.instance.ty().prototype();
debug_struct.field("prototype", prototype);
}
debug_struct.finish()
}
}
impl Display for PackageMeta {
#[inline]
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.name())?;
if formatter.alternate() {
formatter.write_fmt(format_args!("@{}", self.version()))?;
}
Ok(())
}
}
impl PackageMeta {
#[inline(always)]
fn new(origin: &'static RustOrigin, declaration: PackageDeclaration) -> Self {
Self {
origin,
declaration,
modules: RwLock::new(AHashSet::new()),
}
}
pub fn of(name: &str, version: &str) -> Option<&'static Self> {
let registry = PackageRegistry::get();
let version_set = registry.index.get(name)?;
let requirement = VersionReq::parse(version).ok()?;
let mut candidate: Option<(&Version, &PackageMeta)> = None;
for (version, variant) in version_set {
if !requirement.matches(version) {
continue;
}
match candidate {
Some((previous, _)) if previous > version => (),
_ => candidate = Some((version, variant)),
}
}
let (_, meta) = candidate?;
Some(meta)
}
#[inline(always)]
pub(crate) fn by_id(id: Id) -> Option<&'static Self> {
let registry = ModuleRegistry::get();
Some(*registry.index.get(&id)?)
}
#[inline(always)]
pub fn origin(&self) -> &'static RustOrigin {
self.origin
}
#[inline(always)]
pub fn name(&self) -> &'static str {
self.declaration.name
}
#[inline(always)]
pub fn version(&self) -> &'static str {
self.declaration.version
}
#[inline(always)]
pub fn doc(&self) -> Option<&'static str> {
self.declaration.doc
}
#[inline(always)]
pub fn ty(&self) -> &'static TypeMeta {
self.declaration.instance.deref().ty()
}
#[inline(always)]
pub fn instance(&self) -> Cell {
self.declaration.instance.deref().clone()
}
#[inline(always)]
pub(crate) unsafe fn attach_module(&'static self, id: Id) {
let mut modules = self
.modules
.write()
.unwrap_or_else(|poison| poison.into_inner());
if !modules.insert(id) {
unsafe { debug_unreachable!("Duplicate module.") }
}
let registry = ModuleRegistry::get();
if registry.index.insert(id, self).is_some() {
unsafe { debug_unreachable!("Duplicate module.") }
}
}
#[inline(always)]
pub(crate) unsafe fn detach_module(&'static self, id: Id) {
let mut modules = self
.modules
.write()
.unwrap_or_else(|poison| poison.into_inner());
if !modules.remove(&id) {
unsafe { debug_unreachable!("Missing module.") }
}
let registry = ModuleRegistry::get();
if registry.index.remove(&id).is_none() {
unsafe { debug_unreachable!("Missing module.") }
}
}
}
struct PackageRegistry {
index: AHashMap<&'static str, AHashMap<Version, PackageMeta>>,
}
impl PackageRegistry {
#[inline(always)]
fn get() -> &'static Self {
static REGISTRY: Lazy<PackageRegistry> = Lazy::new(|| {
let mut index = AHashMap::<&'static str, AHashMap<Version, PackageMeta>>::new();
for group in DeclarationGroup::enumerate() {
let origin = group.origin;
for declaration in &group.packages {
let declaration = declaration();
let version_set = index.entry(declaration.name).or_default();
let version = match Version::parse(declaration.version) {
Ok(version) => version,
Err(error) => {
let name = declaration.name;
let version = &declaration.version;
origin.blame(&format!(
"Package {name}@{version} version parse error. {error}",
))
}
};
if let Some(previous) = version_set.get(&version) {
let name = declaration.name;
let version = &declaration.version;
let previous = previous.origin;
origin.blame(&format!(
"Package {name}@{version} already declared in {previous}.",
))
}
let meta = PackageMeta::new(origin, declaration);
if let Some(_) = version_set.insert(version, meta) {
unsafe { debug_unreachable!("Duplicate package entry.") }
}
}
}
PackageRegistry { index }
});
REGISTRY.deref()
}
}
struct ModuleRegistry {
index: Table<Id, &'static PackageMeta, RandomState>,
}
impl ModuleRegistry {
fn get() -> &'static Self {
static REGISTRY: Lazy<ModuleRegistry> = Lazy::new(|| ModuleRegistry {
index: Table::new(),
});
®ISTRY
}
}