use std::borrow::Cow;
use std::collections::{hash_set::Iter as HashSetIter, HashMap, HashSet};
use std::iter::FusedIterator;
use std::slice::Iter as SliceIter;
use crate::models::schema::Dependency;
use crate::{InterpreterError, Name};
use super::{
schema::{NamespaceId, SchemaId},
IdentType, TypeIdent,
};
#[derive(Default, Debug)]
pub struct IdentCache {
schemas: HashMap<SchemaId, SchemaEntry>,
unknown_schema: HashMap<NamespaceId, SchemaEntry>,
namespaces: HashMap<NamespaceId, HashSet<SchemaId>>,
global_namespaces: Vec<NamespaceId>,
}
#[derive(Debug)]
pub struct SchemaSetIter<'a> {
cache: &'a IdentCache,
visited: HashSet<SchemaId>,
emitted: HashSet<(IdentType, Cow<'a, str>)>,
types_iter: Option<TypesIterTuple<'a>>,
dependencies_iter: Vec<SliceIter<'a, Dependency<SchemaId>>>,
}
#[derive(Debug)]
struct SchemaEntry {
ns: NamespaceId,
schema: SchemaId,
types: HashSet<(IdentType, Cow<'static, str>)>,
dependencies: Vec<Dependency<SchemaId>>,
}
type TypesIterTuple<'a> = (
NamespaceId,
SchemaId,
HashSetIter<'a, (IdentType, Cow<'static, str>)>,
);
impl IdentCache {
#[inline]
pub fn insert(&mut self, ident: TypeIdent) {
let entry = if ident.schema.is_unknown() {
self.unknown_schema
.entry(ident.ns)
.or_insert_with(|| SchemaEntry {
ns: ident.ns,
schema: SchemaId::UNKNOWN,
types: HashSet::new(),
dependencies: Vec::new(),
})
} else {
self.schemas
.entry(ident.schema)
.or_insert_with(|| SchemaEntry {
ns: ident.ns,
schema: ident.schema,
types: HashSet::new(),
dependencies: Vec::new(),
})
};
entry.types.insert((ident.type_, ident.name.into()));
}
pub fn add_schema(&mut self, ns: NamespaceId, schema: SchemaId) {
self.schemas.entry(schema).or_insert_with(|| SchemaEntry {
ns,
schema,
types: HashSet::new(),
dependencies: Vec::new(),
});
self.namespaces.entry(ns).or_default().insert(schema);
}
pub fn add_dependency(&mut self, schema: SchemaId, dependency: Dependency<SchemaId>) -> bool {
if let Some(entry) = self.schemas.get_mut(&schema) {
if !entry.dependencies.contains(&dependency) {
entry.dependencies.push(dependency);
return true;
}
}
false
}
pub fn add_global_namespace(&mut self, ns: NamespaceId) {
self.namespaces.entry(ns).or_default();
self.global_namespaces.push(ns);
}
pub fn resolve(&self, ident: TypeIdent) -> Result<TypeIdent, InterpreterError> {
let schemas = match (ident.ns, ident.schema) {
(NamespaceId::UNKNOWN, SchemaId::UNKNOWN) => self.schemas.keys().copied().collect(),
(ns, SchemaId::UNKNOWN) => self
.namespaces
.get(&ns)
.into_iter()
.flatten()
.copied()
.collect(),
(_, schema) => vec![schema],
};
let mut ret = None;
for schema in schemas {
if let Some(entry) = self.schemas.get(&schema) {
if entry.matches(&ident) {
if ret.is_some() {
return Err(InterpreterError::AmbiguousType(ident));
}
ret = Some(entry.make_ident(ident.clone()));
}
}
}
if ident.schema.is_unknown() {
for entry in self.unknown_schema.values() {
if entry.matches(&ident) {
if ret.is_some() {
return Err(InterpreterError::AmbiguousType(ident));
}
ret = Some(entry.make_ident(ident.clone()));
}
}
}
if let Some(resolved_ident) = ret {
Ok(resolved_ident)
} else {
Err(InterpreterError::UnknownType(ident))
}
}
pub fn resolve_allow_unknown(&self, ident: TypeIdent) -> Result<TypeIdent, InterpreterError> {
match self.resolve(ident) {
Ok(ident) => Ok(ident),
Err(InterpreterError::UnknownType(ident)) => Ok(ident),
Err(error) => Err(error),
}
}
#[must_use]
pub fn schema_set(&self, schema: SchemaId) -> SchemaSetIter<'_> {
SchemaSetIter::new(self, schema)
}
pub fn resolve_for_schema(
&self,
schema: SchemaId,
ident: TypeIdent,
) -> Result<TypeIdent, InterpreterError> {
let mut visited = HashSet::new();
if let Some(entry) = self.search_in_schema(&mut visited, schema, &ident) {
return Ok(entry.make_ident(ident));
}
for ns in &self.global_namespaces {
for schema in self.namespaces.get(ns).into_iter().flatten() {
if let Some(entry) = self.search_in_schema(&mut visited, *schema, &ident) {
return Ok(entry.make_ident(ident));
}
}
}
for entry in self.unknown_schema.values() {
if entry.matches(&ident) {
return Ok(entry.make_ident(ident));
}
}
Err(InterpreterError::UnknownType(ident))
}
fn search_in_schema(
&self,
visited: &mut HashSet<SchemaId>,
schema: SchemaId,
ident: &TypeIdent,
) -> Option<&SchemaEntry> {
if !visited.insert(schema) {
return None;
}
let entry = self.schemas.get(&schema)?;
if entry.matches(ident) {
return Some(entry);
}
for dep in &entry.dependencies {
if let Some(found) = self.search_in_schema(visited, **dep, ident) {
return Some(found);
}
}
None
}
}
impl SchemaEntry {
fn matches(&self, ident: &TypeIdent) -> bool {
let ns_matches = ident.ns.is_unknown() || ident.ns == self.ns;
let schema_matches = ident.schema.is_unknown() || ident.schema == self.schema;
let contains_type = self
.types
.contains(&(ident.type_, Cow::Borrowed(ident.name.as_str())));
ns_matches && schema_matches && contains_type
}
fn make_ident(&self, ident: TypeIdent) -> TypeIdent {
TypeIdent {
ns: ident.ns.or(self.ns),
schema: ident.schema.or(self.schema),
type_: ident.type_,
name: ident.name,
}
}
}
impl<'a> SchemaSetIter<'a> {
fn new(cache: &'a IdentCache, schema: SchemaId) -> Self {
let schema = cache.schemas.get(&schema);
let types_iter = schema.map(|x| (x.ns, x.schema, x.types.iter()));
let dependencies_iter = schema.map(|x| x.dependencies.iter()).into_iter().collect();
Self {
cache,
visited: HashSet::new(),
emitted: HashSet::new(),
types_iter,
dependencies_iter,
}
}
}
impl Iterator for SchemaSetIter<'_> {
type Item = TypeIdent;
fn next(&mut self) -> Option<Self::Item> {
#[allow(clippy::redundant_else)]
loop {
if let Some((ns, schema, types_iter)) = &mut self.types_iter {
if let Some((type_, name)) = types_iter.next() {
if !self.emitted.insert((*type_, Cow::Borrowed(name.as_ref()))) {
continue;
}
break Some(TypeIdent {
ns: *ns,
schema: *schema,
type_: *type_,
name: Name::new_named(name.clone()),
});
} else {
self.types_iter = None;
}
} else if let Some(dependencies_iter) = self.dependencies_iter.last_mut() {
if let Some(dep) = dependencies_iter.next() {
if self.visited.insert(**dep) {
let Some(entry) = self.cache.schemas.get(&**dep) else {
continue;
};
self.types_iter = Some((entry.ns, entry.schema, entry.types.iter()));
self.dependencies_iter.push(entry.dependencies.iter());
}
} else {
self.dependencies_iter.pop();
}
} else {
break None;
}
}
}
}
impl FusedIterator for SchemaSetIter<'_> {}