use crate::collections::TryCow;
use crate::error::{Result, bail};
use crate::{Atom, StringPool, prelude::*};
use alloc::sync::Arc;
use core::borrow::Borrow;
use core::hash::Hash;
use semver::Version;
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct NameMap<K, V>
where
K: TryClone + Hash + Eq + Ord,
{
definitions: TryIndexMap<K, V>,
alternate_lookups: TryIndexMap<K, (K, TryVersion)>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct TryVersion(Arc<Version>);
impl TryFrom<Version> for TryVersion {
type Error = OutOfMemory;
fn try_from(value: Version) -> Result<Self, Self::Error> {
Ok(Self(try_new::<Arc<_>>(value)?))
}
}
impl TryClone for TryVersion {
#[inline]
fn try_clone(&self) -> Result<Self, OutOfMemory> {
Ok(Self(self.0.clone()))
}
}
impl core::ops::Deref for TryVersion {
type Target = Version;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Borrow<Version> for TryVersion {
#[inline]
fn borrow(&self) -> &Version {
&self.0
}
}
impl serde::Serialize for TryVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for TryVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let v = Version::deserialize(deserializer)?;
let v = try_new::<Arc<_>>(v).map_err(|oom| D::Error::custom(oom))?;
Ok(Self(v))
}
}
impl<K, V> TryClone for NameMap<K, V>
where
K: TryClone + Hash + Eq + Ord,
V: TryClone,
{
fn try_clone(&self) -> Result<Self, OutOfMemory> {
Ok(Self {
definitions: self.definitions.try_clone()?,
alternate_lookups: self.alternate_lookups.try_clone()?,
})
}
}
impl<K, V> NameMap<K, V>
where
K: TryClone + Hash + Eq + Ord,
{
pub fn insert<I>(&mut self, name: &str, cx: &mut I, allow_shadowing: bool, item: V) -> Result<K>
where
I: NameMapIntern<Key = K>,
I::BorrowedKey: Hash + Eq,
{
let key = cx.intern(name)?;
if !allow_shadowing && self.definitions.contains_key(&key) {
bail!("map entry `{name}` defined twice")
}
self.definitions.insert(key.try_to_owned()?, item)?;
if let Some((alternate_key, version)) = alternate_lookup_key(name) {
let alternate_key = cx.intern(alternate_key)?;
let version = TryVersion::try_from(version)?;
if let Some((prev_key, prev_version)) = self.alternate_lookups.insert(
alternate_key.try_clone()?,
(key.try_clone()?, version.clone()),
)? {
if version < prev_version {
self.alternate_lookups
.insert(alternate_key, (prev_key, prev_version))?;
}
}
}
Ok(key)
}
pub fn get<I>(&self, name: &str, cx: &I) -> Option<&V>
where
I: NameMapIntern<Key = K>,
I::Key: Borrow<I::BorrowedKey>,
I::BorrowedKey: Hash + Eq,
{
let candidate = cx.lookup(name).and_then(|k| self.definitions.get(&*k));
if let Some(def) = candidate {
return Some(def);
}
let (alternate_name, _version) = alternate_lookup_key(name)?;
let alternate_key = cx.lookup(alternate_name)?;
let (exact_key, _version) = self.alternate_lookups.get(&alternate_key)?;
self.definitions.get(exact_key.borrow())
}
pub fn raw_iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.definitions.iter()
}
pub fn raw_get_mut(&mut self, key: &K) -> Option<&mut V> {
self.definitions.get_mut(key)
}
}
impl<K, V> Default for NameMap<K, V>
where
K: TryClone + Hash + Eq + Ord,
{
fn default() -> NameMap<K, V> {
NameMap {
definitions: Default::default(),
alternate_lookups: Default::default(),
}
}
}
pub trait NameMapIntern {
type Key: Borrow<Self::BorrowedKey>;
type BorrowedKey: ?Sized + TryToOwned<Owned = Self::Key>;
fn intern(&mut self, s: &str) -> Result<Self::Key, OutOfMemory>;
fn lookup<'a>(&'a self, s: &'a str) -> Option<TryCow<'a, Self::BorrowedKey>>;
}
pub struct NameMapNoIntern;
impl NameMapIntern for NameMapNoIntern {
type Key = TryString;
type BorrowedKey = str;
fn intern(&mut self, s: &str) -> Result<Self::Key, OutOfMemory> {
TryString::try_from(s)
}
fn lookup<'a>(&'a self, s: &'a str) -> Option<TryCow<'a, Self::BorrowedKey>> {
Some(TryCow::Borrowed(s))
}
}
impl NameMapIntern for StringPool {
type Key = Atom;
type BorrowedKey = Atom;
fn intern(&mut self, string: &str) -> Result<Atom, OutOfMemory> {
self.insert(string)
}
fn lookup(&self, string: &str) -> Option<TryCow<'_, Atom>> {
self.get_atom(string).map(TryCow::Owned)
}
}
fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
let at = name.find('@')?;
let version_string = &name[at + 1..];
let version = Version::parse(version_string).ok()?;
if !version.pre.is_empty() {
None
} else if version.major != 0 {
let first_dot = version_string.find('.')? + at + 1;
Some((&name[..first_dot], version))
} else if version.minor != 0 {
let first_dot = version_string.find('.')? + at + 1;
let second_dot = name[first_dot + 1..].find('.')? + first_dot + 1;
Some((&name[..second_dot], version))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::{NameMap, NameMapNoIntern};
#[test]
fn alternate_lookup_key() {
fn alt(s: &str) -> Option<&str> {
super::alternate_lookup_key(s).map(|(s, _)| s)
}
assert_eq!(alt("x"), None);
assert_eq!(alt("x:y/z"), None);
assert_eq!(alt("x:y/z@1.0.0"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@1.1.0"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@1.1.2"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@2.1.2"), Some("x:y/z@2"));
assert_eq!(alt("x:y/z@2.1.2+abc"), Some("x:y/z@2"));
assert_eq!(alt("x:y/z@0.1.2"), Some("x:y/z@0.1"));
assert_eq!(alt("x:y/z@0.1.3"), Some("x:y/z@0.1"));
assert_eq!(alt("x:y/z@0.2.3"), Some("x:y/z@0.2"));
assert_eq!(alt("x:y/z@0.2.3+abc"), Some("x:y/z@0.2"));
assert_eq!(alt("x:y/z@0.0.1"), None);
assert_eq!(alt("x:y/z@0.0.1-pre"), None);
assert_eq!(alt("x:y/z@0.1.0-pre"), None);
assert_eq!(alt("x:y/z@1.0.0-pre"), None);
}
#[test]
fn name_map_smoke() {
let mut map = NameMap::default();
let mut intern = NameMapNoIntern;
map.insert("a", &mut intern, false, 0).unwrap();
map.insert("b", &mut intern, false, 1).unwrap();
assert!(map.insert("a", &mut intern, false, 0).is_err());
assert!(map.insert("a", &mut intern, true, 0).is_ok());
assert_eq!(map.get("a", &intern), Some(&0));
assert_eq!(map.get("b", &intern), Some(&1));
assert_eq!(map.get("c", &intern), None);
map.insert("a:b/c@1.0.0", &mut intern, false, 2).unwrap();
map.insert("a:b/c@1.0.1", &mut intern, false, 3).unwrap();
assert_eq!(map.get("a:b/c@1.0.0", &intern), Some(&2));
assert_eq!(map.get("a:b/c@1.0.1", &intern), Some(&3));
assert_eq!(map.get("a:b/c@1.0.2", &intern), Some(&3));
assert_eq!(map.get("a:b/c@1.1.0", &intern), Some(&3));
}
}