use std::{
collections::HashMap,
fmt::{self, Debug, Display},
marker::PhantomData,
};
use ambient_package::{Identifier, PascalCaseIdentifier, SnakeCaseIdentifier};
use ambient_std::topological_sort::{topological_sort, TopologicalSortable};
use thiserror::Error;
use ulid::Ulid;
use crate::{Attribute, Component, Concept, Message, Package, Scope, Type, TypeInner};
#[derive(Error, Debug)]
pub enum GetScopeError {
#[error(
"Failed to find scope `{segment}` in scope `{scope_path}` while searching for `{path:?}`"
)]
NotFound {
segment: SnakeCaseIdentifier,
scope_path: String,
path: Vec<SnakeCaseIdentifier>,
},
}
#[derive(Clone, PartialEq, Debug, Default)]
pub struct ItemMap {
items: HashMap<Ulid, ItemValue>,
vec_items: HashMap<ItemId<Type>, ItemId<Type>>,
option_items: HashMap<ItemId<Type>, ItemId<Type>>,
}
impl ItemMap {
pub fn add<T: Item>(&mut self, item: T) -> ItemId<T> {
if T::TYPE == Type::TYPE {
let data = item.data().clone();
let raw_new_id = self.add_raw(item);
let new_id = ItemId(raw_new_id.0, PhantomData::<Type>);
let vec_id = self.add_raw(Type::new(
ItemData {
id: PascalCaseIdentifier::new(&format!("Vec{}", data.id))
.unwrap()
.into(),
..data
},
TypeInner::Vec(new_id),
));
self.vec_items.insert(new_id, vec_id);
let option_id = self.add_raw(Type::new(
ItemData {
id: PascalCaseIdentifier::new(&format!("Option{}", data.id))
.unwrap()
.into(),
..data
},
TypeInner::Option(new_id),
));
self.option_items.insert(new_id, option_id);
raw_new_id
} else {
self.add_raw(item)
}
}
#[allow(clippy::disallowed_methods)]
fn add_raw<T: Item>(&mut self, value: T) -> ItemId<T> {
let ulid = ulid::Ulid::new();
self.items.insert(ulid, value.into_item_value());
ItemId(ulid, PhantomData)
}
pub fn get<T: Item>(&self, id: ItemId<T>) -> &T {
T::from_item_value(self.items.get(&id.0).unwrap()).unwrap()
}
pub fn get_mut<T: Item>(&mut self, id: ItemId<T>) -> &mut T {
T::from_item_value_mut(self.items.get_mut(&id.0).unwrap()).unwrap()
}
pub fn insert<T: Item>(&mut self, id: ItemId<T>, item: T) {
self.items.insert(id.0, item.into_item_value());
}
pub fn get_vec_id(&self, id: ItemId<Type>) -> ItemId<Type> {
self.vec_items.get(&id).copied().unwrap()
}
pub fn get_option_id(&self, id: ItemId<Type>) -> ItemId<Type> {
self.option_items.get(&id).copied().unwrap()
}
pub fn get_scope_id(
&self,
start_scope_id: ItemId<Scope>,
mut path: &[SnakeCaseIdentifier],
) -> Result<ItemId<Scope>, GetScopeError> {
let mut scope_id = start_scope_id;
if let Some(first_segment) = path.first() {
if let Some(package_id) = self.get(scope_id).imports.get(first_segment) {
scope_id = self.get(*package_id).scope_id;
path = &path[1..];
}
}
for segment in path {
let scope = self.get(scope_id);
scope_id =
scope
.scopes
.get(segment)
.copied()
.ok_or_else(|| GetScopeError::NotFound {
segment: segment.clone(),
scope_path: self.fully_qualified_display_path(scope, None, None),
path: path.to_vec(),
})?;
}
Ok(scope_id)
}
pub fn get_scope(
&self,
start_scope_id: ItemId<Scope>,
path: &[SnakeCaseIdentifier],
) -> anyhow::Result<&Scope> {
Ok(self.get(self.get_scope_id(start_scope_id, path)?))
}
pub(crate) fn get_or_create_scope_mut(
&mut self,
start_scope_id: ItemId<Scope>,
path: &[SnakeCaseIdentifier],
) -> ItemId<Scope> {
let mut scope_id = start_scope_id;
for segment in path.iter() {
let existing_id = self.get(scope_id).scopes.get(segment).copied();
scope_id = match existing_id {
Some(id) => id,
None => {
let parent_scope_data = self.get(scope_id).data().clone();
let new_id = self.add(Scope::new(ItemData {
parent_id: Some(scope_id),
id: segment.clone().into(),
..parent_scope_data
}));
self.get_mut(scope_id)
.scopes
.insert(segment.clone(), new_id);
new_id
}
};
}
scope_id
}
pub fn fully_qualified_display_path_impl<T: Item>(
&self,
item: &T,
separator: &str,
(type_prefix, source_suffix): (bool, bool),
relative_to: Option<ItemId<Scope>>,
item_prefix: Option<&str>,
) -> String {
let data = item.data();
let mut path = vec![format!(
"{}{}",
item_prefix.unwrap_or_default(),
data.id.as_str()
)];
let mut parent_id = data.parent_id;
while let Some(this_parent_id) = parent_id {
if let Some(relative_to) = relative_to {
if this_parent_id == relative_to {
break;
}
}
let parent = self.get(this_parent_id);
let id = parent.data().id.to_string();
if !id.is_empty() {
path.push(id);
}
parent_id = parent.data().parent_id;
}
path.reverse();
let prefix = if type_prefix {
format!("{}:", T::TYPE.to_string().to_lowercase())
} else {
"".to_string()
};
format!(
"{}{}{}",
prefix,
path.join(separator),
if source_suffix {
format!(" ({:?})", data.source)
} else {
"".to_string()
}
)
}
pub fn fully_qualified_display_path<T: Item>(
&self,
item: &T,
relative_to: Option<ItemId<Scope>>,
item_prefix: Option<&str>,
) -> String {
self.fully_qualified_display_path_impl(item, "::", (false, false), relative_to, item_prefix)
}
pub fn scope_and_dependencies(&self, id: ItemId<Package>) -> Vec<ItemId<Package>> {
impl TopologicalSortable<ItemMap> for ItemId<Package> {
fn dependencies(&self, items: &ItemMap) -> Vec<Self> {
let item = items.get(*self);
item.dependencies.values().map(|d| d.id).collect()
}
fn id(&self, items: &ItemMap) -> String {
items.get(*self).data.id.to_string()
}
}
topological_sort(std::iter::once(id), self).unwrap()
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ItemType {
Component,
Concept,
Message,
Type,
Attribute,
Scope,
Package,
}
impl Display for ItemType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(self, f)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum ItemValue {
Component(Component),
Concept(Concept),
Message(Message),
Type(Type),
Attribute(Attribute),
Scope(Scope),
Package(Package),
}
#[derive(Clone, PartialEq, Debug, Eq)]
pub struct ItemData {
pub parent_id: Option<ItemId<Scope>>,
pub id: Identifier,
pub source: ItemSource,
}
#[derive(Copy, Clone, PartialEq, Debug, Eq)]
pub enum ItemSource {
System,
Ambient,
User,
}
pub trait Item: Clone {
const TYPE: ItemType;
type Unresolved: Eq + Debug;
fn from_item_value(value: &ItemValue) -> Option<&Self>;
fn from_item_value_mut(value: &mut ItemValue) -> Option<&mut Self>;
fn into_item_value(self) -> ItemValue;
fn data(&self) -> &ItemData;
}
pub struct ItemId<T: Item>(Ulid, PhantomData<T>);
impl<T: Item> std::hash::Hash for ItemId<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.1.hash(state);
}
}
impl<T: Item> Copy for ItemId<T> {}
impl<T: Item> Clone for ItemId<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Item> Debug for ItemId<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ItemId<{}>({:?})", std::any::type_name::<T>(), self.0)
}
}
impl<T: Item> Display for ItemId<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(self, f)
}
}
impl<T: Item> PartialEq for ItemId<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T: Item> Eq for ItemId<T> {}
impl<T: Item> ItemId<T> {
pub(crate) fn empty_you_should_really_initialize_this() -> Self {
Self(Ulid::default(), PhantomData)
}
}
#[derive(Clone)]
pub enum ResolvableItemId<T: Item> {
Unresolved(T::Unresolved),
Resolved(ItemId<T>),
}
impl<T: Item> ResolvableItemId<T> {
pub fn as_resolved(&self) -> Option<ItemId<T>> {
match self {
Self::Unresolved(_) => None,
Self::Resolved(id) => Some(*id),
}
}
}
impl<T: Item + Debug> Debug for ResolvableItemId<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unresolved(arg0) => write!(f, "Unresolved({arg0:?})"),
Self::Resolved(arg0) => write!(f, "Resolved({arg0:?})"),
}
}
}
impl<T: Item> PartialEq for ResolvableItemId<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Unresolved(l0), Self::Unresolved(r0)) => l0 == r0,
(Self::Resolved(l0), Self::Resolved(r0)) => l0 == r0,
_ => false,
}
}
}
impl<T: Item> Eq for ResolvableItemId<T> {}
impl<T: Item> std::hash::Hash for ResolvableItemId<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
}
}