use super::{
BindingRef, FileArtifact, Namespace, Reference, ReferenceTarget, Scope, ScopeId, ScriptUnit,
};
use vize_l0::Span;
mod template;
pub use template::{
HandlerLocalRef, TemplateQueryError, TemplateSiteRef, TemplateSiteScope, TemplateSymbolRef,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PositionQueryError {
IncompleteFile,
OutOfBounds,
NotCharBoundary,
AmbiguousSite,
}
#[derive(Clone, Copy)]
pub struct ReferenceRef<'file, 'arena> {
file: &'file FileArtifact<'arena>,
reference: &'file Reference,
}
impl<'file, 'arena> ReferenceRef<'file, 'arena> {
#[must_use]
pub fn file(self) -> &'file FileArtifact<'arena> {
self.file
}
#[must_use]
pub fn reference(self) -> &'file Reference {
self.reference
}
#[must_use]
pub fn binding(self) -> Option<BindingRef<'file, 'arena>> {
match self.reference.target {
ReferenceTarget::Resolved(id) => self.file.binding(id),
ReferenceTarget::Unresolved => None,
}
}
}
#[derive(Clone, Copy)]
pub struct ScopeRef<'file, 'arena> {
file: &'file FileArtifact<'arena>,
scope: &'file Scope,
}
impl<'file, 'arena> ScopeRef<'file, 'arena> {
#[must_use]
pub fn file(self) -> &'file FileArtifact<'arena> {
self.file
}
#[must_use]
pub fn scope(self) -> &'file Scope {
self.scope
}
#[must_use]
pub fn lookup(self, name: &str, namespace: Namespace) -> Option<BindingRef<'file, 'arena>> {
self.file.lookup(self.scope.id, name, namespace)
}
}
impl<'arena> FileArtifact<'arena> {
pub fn reference_at_offset(
&self,
offset: u32,
) -> Result<Option<ReferenceRef<'_, 'arena>>, PositionQueryError> {
let Some(unit) = self.query_unit(offset)? else {
return Ok(None);
};
self.query_reference(unit, offset)
}
pub fn binding_at_offset(
&self,
offset: u32,
) -> Result<Option<BindingRef<'_, 'arena>>, PositionQueryError> {
let Some(unit) = self.query_unit(offset)? else {
return Ok(None);
};
let declaration = self.query_declaration(unit, offset)?;
let reference = self.query_reference(unit, offset)?;
match (declaration, reference) {
(Some(_), Some(_)) => Err(PositionQueryError::AmbiguousSite),
(Some(binding), None) => Ok(Some(binding)),
(None, Some(reference)) => Ok(reference.binding()),
(None, None) => Ok(None),
}
}
pub fn scope_at_offset(
&self,
offset: u32,
) -> Result<Option<ScopeRef<'_, 'arena>>, PositionQueryError> {
let Some(unit) = self.query_unit(offset)? else {
return Ok(None);
};
let declaration = self.query_declaration(unit, offset)?;
let reference = self.query_reference(unit, offset)?;
let site_scope = match (declaration, reference) {
(Some(_), Some(_)) => return Err(PositionQueryError::AmbiguousSite),
(Some(binding), None) => binding.declaration().map(|row| row.scope),
(None, Some(reference)) => Some(reference.reference.scope),
(None, None) => None,
};
let Some(mut scope) = self
.facts
.scopes
.get(site_scope.unwrap_or(unit.scope).index() as usize)
else {
return Ok(None);
};
if site_scope.is_none() {
for candidate in &self.facts.scopes {
if !contains(candidate.span, offset)
|| !self.scope_descends(candidate.id, unit.scope)
{
continue;
}
if self.scope_descends(candidate.id, scope.id) {
scope = candidate;
} else if !self.scope_descends(scope.id, candidate.id) {
return Err(PositionQueryError::AmbiguousSite);
}
}
}
Ok(Some(ScopeRef { file: self, scope }))
}
fn query_unit(&self, offset: u32) -> Result<Option<&ScriptUnit>, PositionQueryError> {
if !self.is_complete() {
return Err(PositionQueryError::IncompleteFile);
}
let source = self.artifact().source();
if offset as usize > source.len() {
return Err(PositionQueryError::OutOfBounds);
}
if !source.is_char_boundary(offset as usize) {
return Err(PositionQueryError::NotCharBoundary);
}
unique(
self.units()
.iter()
.filter(|unit| contains(unit.span, offset)),
)
}
fn query_reference(
&self,
unit: &ScriptUnit,
offset: u32,
) -> Result<Option<ReferenceRef<'_, 'arena>>, PositionQueryError> {
unique(
self.references()
.iter()
.filter(|row| row.unit == unit.id && contains(row.span, offset)),
)
.map(|row| {
row.map(|reference| ReferenceRef {
file: self,
reference,
})
})
}
fn query_declaration(
&self,
unit: &ScriptUnit,
offset: u32,
) -> Result<Option<BindingRef<'_, 'arena>>, PositionQueryError> {
unique(
self.facts
.declarations
.iter()
.filter(|row| row.unit == unit.id && contains(row.span, offset)),
)
.map(|row| row.and_then(|declaration| self.binding(declaration.id)))
}
fn scope_descends(&self, mut scope: ScopeId, ancestor: ScopeId) -> bool {
for _ in 0..self.facts.scopes.len() {
if scope == ancestor {
return true;
}
let Some(parent) = self
.facts
.scopes
.get(scope.index() as usize)
.and_then(|row| row.parent)
else {
return false;
};
scope = parent;
}
false
}
}
fn contains(span: Span, offset: u32) -> bool {
span.start <= offset && offset < span.end
}
fn unique<T>(mut rows: impl Iterator<Item = T>) -> Result<Option<T>, PositionQueryError> {
let row = rows.next();
if rows.next().is_some() {
Err(PositionQueryError::AmbiguousSite)
} else {
Ok(row)
}
}
#[cfg(test)]
mod tests;