use std::collections::HashMap;
use yog_ids::script_problem;
use super::Resolver;
use super::naming::Case;
use crate::ast::Name;
use crate::source::token::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DefKind {
Constant,
ReadOnly,
Variable,
Param,
Method,
Type,
Case,
Field,
Content,
Component,
Import,
Lifetime,
}
impl DefKind {
pub fn local(mutable: bool) -> Self {
if mutable {
DefKind::Variable
} else {
DefKind::ReadOnly
}
}
pub fn assignable(self) -> bool {
matches!(self, DefKind::Variable | DefKind::Param | DefKind::Field)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Def {
pub name: String,
pub span: Span,
pub kind: DefKind,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Symbols {
pub defs: Vec<Def>,
pub refs: Vec<(Span, usize)>,
}
impl Symbols {
pub fn def_at(&self, offset: u32) -> Option<&Def> {
let inside = |s: Span| s.start <= offset && offset < s.end;
self.refs
.iter()
.find(|(s, _)| inside(*s))
.and_then(|(_, d)| self.defs.get(*d))
.or_else(|| self.defs.iter().find(|d| inside(d.span)))
}
}
#[derive(Default)]
pub(super) struct Scope {
names: HashMap<String, usize>,
}
impl Resolver<'_, '_> {
pub(super) fn scoped<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
self.scopes.push(Scope::default());
let out = f(self);
self.scopes.pop();
out
}
pub(super) fn define(&mut self, name: &Name, kind: DefKind, case: Case, again: bool) {
let id = self.symbols.defs.len();
let Some(scope) = self.scopes.last_mut() else {
return;
};
if let Some(&first) = scope.names.get(&name.text) {
if !again {
let at = self.symbols.defs[first].span;
let line = self.report.line_of(at);
self.report.error(
script_problem::DUPLICATE,
name.span,
format!("`{}` is declared already (line {line})", name.text),
);
}
return;
}
scope.names.insert(name.text.clone(), id);
self.symbols.defs.push(Def {
name: name.text.clone(),
span: name.span,
kind,
});
self.check_case(name, case);
}
pub(super) fn bind(&mut self, name: &Name, kind: DefKind) {
let id = self.symbols.defs.len();
if let Some(scope) = self.scopes.last_mut() {
scope.names.insert(name.text.clone(), id);
self.symbols.defs.push(Def {
name: name.text.clone(),
span: name.span,
kind,
});
}
self.check_case(name, Case::Camel);
}
pub(super) fn lookup(&self, name: &str) -> Option<usize> {
self.scopes
.iter()
.rev()
.find_map(|s| s.names.get(name).copied())
}
pub(super) fn refer(&mut self, name: &str, span: Span) -> Option<DefKind> {
let id = self.lookup(name)?;
self.symbols.refs.push((span, id));
Some(self.symbols.defs[id].kind)
}
}