use std::collections::HashMap;
use crate::ast::{Decl, DeclKind, Modifier, Name, Type};
use crate::source::token::Span;
use crate::words::{class, interface, record};
pub(super) struct Info<'d> {
pub name: &'d str,
pub arity: usize,
pub span: Span,
pub class: bool,
pub interface: bool,
pub is_abstract: bool,
pub sealed: bool,
pub readonly: bool,
pub bases: Vec<(&'d Name, usize)>,
pub members: Vec<Member>,
pub library: bool,
pub intrinsic: bool,
}
pub(super) struct Member {
pub name: String,
pub span: Span,
pub is_abstract: bool,
pub is_virtual: bool,
pub overrides: bool,
pub body: bool,
pub explicit: Option<String>,
pub access: bool,
}
pub(super) struct Index<'d> {
pub types: HashMap<(&'d str, usize), Info<'d>>,
}
impl<'d> Index<'d> {
pub fn collect(decls: &'d [Decl]) -> Self {
let mut index = Index {
types: HashMap::new(),
};
index.walk(decls);
index
}
pub fn mark_library(&mut self) {
self.types.values_mut().for_each(|i| i.library = true);
}
pub fn add(&mut self, decls: &'d [Decl]) {
self.walk(decls);
}
fn walk(&mut self, decls: &'d [Decl]) {
for d in decls {
if let DeclKind::Type {
word,
name,
value,
header,
body,
} = &d.kind
{
let class =
!value && (word.text == class::WORD.text || word.text == record::WORD.text);
let interface = word.text == interface::WORD.text;
let arity = header.generics.iter().filter(|g| !g.lifetime).count();
let info = Info {
name: &name.text,
arity,
span: name.span,
class,
interface,
is_abstract: d.has(Modifier::Abstract),
sealed: d.has(Modifier::Sealed),
readonly: d.has(Modifier::Readonly) || word.text == record::WORD.text,
bases: header.bases.iter().filter_map(plain_base).collect(),
members: body.iter().filter_map(|m| member(m, interface)).collect(),
library: false,
intrinsic: d.attributes.iter().any(|a| {
a.path
.last()
.is_some_and(|n| n.text == yog_script_ids::system::INTRINSIC)
}),
};
self.types.insert((&name.text, arity), info);
}
self.walk(d.kind.members());
}
}
pub fn base(&self, (b, arity): &(&'d Name, usize)) -> Option<&Info<'d>> {
self.types.get(&(b.text.as_str(), *arity))
}
pub fn ancestors(&self, info: &Info<'d>) -> Vec<&Info<'d>> {
let mut out = self.written_ancestors(info);
if let Some(object) = self.types.get(&(yog_script_ids::clr::OBJECT, 0))
&& !same(info, object)
&& !out.iter().any(|o| same(o, object))
{
out.push(object);
}
out
}
fn written_ancestors(&self, start: &Info<'d>) -> Vec<&Info<'d>> {
let mut out: Vec<&Info<'d>> = Vec::new();
let mut next = vec![start];
while let Some(n) = next.pop() {
for b in &n.bases {
if let Some(info) = self.base(b)
&& !same(info, start)
&& !out.iter().any(|o| same(o, info))
{
out.push(info);
next.push(info);
}
}
}
out
}
}
fn same(a: &Info, b: &Info) -> bool {
a.name == b.name && a.arity == b.arity
}
fn plain_base(t: &Type) -> Option<(&Name, usize)> {
match &t.kind {
crate::ast::TypeKind::Path { path, args } => path.last().map(|n| (n, args.len())),
_ => None,
}
}
fn plain_name(t: &Type) -> Option<&Name> {
match &t.kind {
crate::ast::TypeKind::Path { path, .. } => path.last(),
_ => None,
}
}
fn member(d: &Decl, interface: bool) -> Option<Member> {
let mut explicit = None;
let (name, span, body) = match &d.kind {
DeclKind::Method(m) => {
explicit = m
.explicit
.as_ref()
.and_then(plain_name)
.map(|n| n.text.clone());
(m.name.text.as_str(), m.name.span, m.body.is_some())
}
DeclKind::Property {
name,
accessors,
value,
explicit: of,
..
} => {
explicit = of.as_ref().and_then(plain_name).map(|n| n.text.clone());
(
name.text.as_str(),
name.span,
!interface || value.is_some() || accessors.iter().any(|a| a.body.is_some()),
)
}
DeclKind::Indexer(ix) => (
yog_script_ids::clr::INDEXER,
ix.span,
!interface || ix.accessors.iter().any(|a| a.body.is_some()),
),
_ => return None,
};
let body = body && !d.has(Modifier::Abstract);
Some(Member {
name: name.to_string(),
span,
is_abstract: d.has(Modifier::Abstract),
is_virtual: d.has(Modifier::Virtual),
overrides: d.has(Modifier::Override),
body,
explicit,
access: [
Modifier::Public,
Modifier::Private,
Modifier::Protected,
Modifier::Internal,
]
.into_iter()
.any(|m| d.has(m)),
})
}