use sipha::types::Span;
use std::collections::HashMap;
use leekscript_core::doc_comment::DocComment;
use leekscript_core::Type;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ScopeId(pub usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScopeKind {
Main,
Function,
Class,
Loop,
Block,
}
#[derive(Clone, Debug)]
pub struct VariableInfo {
pub name: String,
pub kind: VariableKind,
pub span: Span,
pub declared_type: Option<Type>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VariableKind {
Local,
Global,
Parameter,
}
#[derive(Clone, Debug)]
pub struct FunctionOverload {
pub min_arity: usize,
pub max_arity: usize,
pub span: Span,
pub param_types: Option<Vec<Type>>,
pub return_type: Option<Type>,
}
#[derive(Clone, Debug, Default)]
pub struct SigMeta {
pub doc: Option<DocComment>,
pub complexity: Option<u8>,
}
#[must_use]
pub fn complexity_display_string(code: u8) -> &'static str {
match code {
1 => "O(1)",
2 => "O(log(n))",
3 => "O(√n)",
4 => "O(n)",
5 => "O(nlog*(n))",
6 => "O(nlog(n))",
7 => "O(n²)",
8 => "O(n³)",
9 => "2^poly(log(n))",
10 => "2^poly(n)",
11 => "O(n!)",
12 => "2^2^poly(n)",
13 => "∞",
_ => "?",
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum MemberVisibility {
#[default]
Public,
Protected,
Private,
}
#[derive(Clone, Debug, Default)]
pub struct ClassMembers {
pub fields: HashMap<String, (Type, MemberVisibility)>,
pub methods: HashMap<String, (Vec<Type>, Type, MemberVisibility)>,
pub static_fields: HashMap<String, (Type, MemberVisibility)>,
pub static_methods: HashMap<String, (Vec<Type>, Type, MemberVisibility)>,
}
#[derive(Debug)]
pub struct Scope {
pub kind: ScopeKind,
pub parent: Option<ScopeId>,
variables: HashMap<String, VariableInfo>,
globals: Option<std::collections::HashSet<String>>,
global_types: Option<HashMap<String, Type>>,
functions: Option<HashMap<String, Vec<FunctionOverload>>>,
classes: Option<HashMap<String, Span>>,
}
impl Scope {
#[must_use]
pub fn new_main() -> Self {
Self {
kind: ScopeKind::Main,
parent: None,
variables: HashMap::new(),
globals: Some(std::collections::HashSet::new()),
global_types: Some(HashMap::new()),
functions: Some(HashMap::new()),
classes: Some(HashMap::new()),
}
}
#[must_use]
pub fn new_child(kind: ScopeKind, parent: ScopeId) -> Self {
Self {
kind,
parent: Some(parent),
variables: HashMap::new(),
globals: None,
global_types: None,
functions: None,
classes: None,
}
}
pub fn add_variable(&mut self, info: VariableInfo) {
self.variables.insert(info.name.clone(), info);
}
#[must_use]
pub fn has_variable(&self, name: &str) -> bool {
self.variables.contains_key(name)
}
#[must_use]
pub fn variable_names(&self) -> Vec<String> {
self.variables.keys().cloned().collect()
}
#[must_use]
pub fn global_names(&self) -> Option<Vec<String>> {
self.globals.as_ref().map(|g| g.iter().cloned().collect())
}
#[must_use]
pub fn function_names(&self) -> Option<Vec<String>> {
self.functions.as_ref().map(|f| f.keys().cloned().collect())
}
#[must_use]
pub fn class_names(&self) -> Option<Vec<String>> {
self.classes.as_ref().map(|c| c.keys().cloned().collect())
}
pub fn add_global(&mut self, name: String) {
if let Some(g) = &mut self.globals {
g.insert(name);
}
}
pub fn add_function(&mut self, name: String, min_arity: usize, max_arity: usize, span: Span) {
if let Some(f) = &mut self.functions {
f.entry(name).or_default().push(FunctionOverload {
min_arity,
max_arity,
span,
param_types: None,
return_type: None,
});
}
}
pub fn add_function_with_types(
&mut self,
name: String,
min_arity: usize,
max_arity: usize,
span: Span,
param_types: Option<Vec<Type>>,
return_type: Option<Type>,
) {
if let Some(f) = &mut self.functions {
f.entry(name).or_default().push(FunctionOverload {
min_arity,
max_arity,
span,
param_types,
return_type,
});
}
}
pub fn add_class(&mut self, name: String, span: Span) {
if let Some(c) = &mut self.classes {
let is_empty = span.start >= span.end;
match c.entry(name) {
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(span);
}
std::collections::hash_map::Entry::Occupied(mut e) => {
let existing = e.get();
if existing.start >= existing.end && !is_empty {
e.insert(span);
}
}
}
}
}
#[must_use]
pub fn has_global(&self, name: &str) -> bool {
self.globals.as_ref().is_some_and(|g| g.contains(name))
}
#[must_use]
pub fn function_accepts_arity(&self, name: &str, arity: usize) -> bool {
self.functions.as_ref().is_some_and(|f| {
f.get(name).is_some_and(|ranges| {
ranges
.iter()
.any(|o| o.min_arity <= arity && arity <= o.max_arity)
})
})
}
#[must_use]
pub fn get_function_span(&self, name: &str) -> Option<Span> {
self.functions
.as_ref()
.and_then(|f| f.get(name).and_then(|v| v.first()).map(|o| o.span))
}
#[must_use]
pub fn get_function_span_for_arity_range(
&self,
name: &str,
min_arity: usize,
max_arity: usize,
) -> Option<Span> {
self.functions.as_ref().and_then(|f| {
f.get(name).and_then(|ranges| {
ranges
.iter()
.find(|o| o.min_arity == min_arity && o.max_arity == max_arity)
.map(|o| o.span)
})
})
}
#[must_use]
pub fn get_function_arity(&self, name: &str) -> Option<usize> {
self.functions
.as_ref()
.and_then(|f| f.get(name).and_then(|v| v.first()).map(|o| o.max_arity))
}
#[must_use]
pub fn get_function_type(&self, name: &str, arity: usize) -> Option<(Vec<Type>, Type)> {
let overloads = self.functions.as_ref()?.get(name)?;
let o = overloads
.iter()
.find(|o| o.min_arity <= arity && arity <= o.max_arity)?;
let params = o.param_types.clone()?;
let ret = o.return_type.clone().unwrap_or(Type::any());
Some((params, ret))
}
#[must_use]
pub fn get_function_type_as_value(&self, name: &str) -> Option<Type> {
let overloads = self.functions.as_ref()?.get(name)?;
let o = overloads.first()?;
let ret = o.return_type.clone().unwrap_or(Type::any());
let param_types = o.param_types.clone().unwrap_or_default();
if o.min_arity < o.max_arity && o.min_arity <= param_types.len() {
let mut variants = Vec::with_capacity(o.max_arity - o.min_arity + 1);
for arity in o.min_arity..=o.max_arity {
let args: Vec<Type> = param_types.iter().take(arity).cloned().collect();
variants.push(Type::function(args, ret.clone()));
}
Some(if variants.len() == 1 {
variants.into_iter().next().unwrap()
} else {
Type::compound(variants)
})
} else {
Some(Type::function(param_types, ret))
}
}
pub fn add_global_with_type(&mut self, name: String, ty: Type) {
if let Some(g) = &mut self.globals {
g.insert(name.clone());
}
if let Some(gt) = &mut self.global_types {
gt.insert(name, ty);
}
}
#[must_use]
pub fn get_global_type(&self, name: &str) -> Option<Type> {
self.global_types.as_ref()?.get(name).cloned()
}
#[must_use]
pub fn has_class(&self, name: &str) -> bool {
self.classes.as_ref().is_some_and(|c| c.contains_key(name))
}
#[must_use]
pub fn get_class_first_span(&self, name: &str) -> Option<Span> {
self.classes.as_ref().and_then(|c| c.get(name).copied())
}
#[must_use]
pub fn get_variable(&self, name: &str) -> Option<&VariableInfo> {
self.variables.get(name)
}
}
#[derive(Debug)]
pub struct ScopeStore {
scopes: Vec<Scope>,
class_members: HashMap<String, ClassMembers>,
root_function_meta: HashMap<String, SigMeta>,
root_global_meta: HashMap<String, SigMeta>,
}
impl Default for ScopeStore {
fn default() -> Self {
Self::new()
}
}
impl ScopeStore {
#[must_use]
pub fn new() -> Self {
let mut store = Self {
scopes: Vec::new(),
class_members: HashMap::new(),
root_function_meta: HashMap::new(),
root_global_meta: HashMap::new(),
};
store.scopes.push(Scope::new_main());
store
}
#[must_use]
pub fn root_id(&self) -> ScopeId {
ScopeId(0)
}
#[must_use]
pub fn get(&self, id: ScopeId) -> Option<&Scope> {
self.scopes.get(id.0)
}
pub fn get_mut(&mut self, id: ScopeId) -> Option<&mut Scope> {
self.scopes.get_mut(id.0)
}
pub fn push(&mut self, kind: ScopeKind, parent: ScopeId) -> ScopeId {
let id = ScopeId(self.scopes.len());
self.scopes.push(Scope::new_child(kind, parent));
id
}
pub fn add_root_global(&mut self, name: String) {
if let Some(root) = self.scopes.get_mut(0) {
root.add_global(name);
}
}
pub fn add_root_global_with_type(&mut self, name: String, ty: Type) {
if let Some(root) = self.scopes.get_mut(0) {
root.add_global_with_type(name, ty);
}
}
pub fn add_root_function(
&mut self,
name: String,
min_arity: usize,
max_arity: usize,
span: Span,
) {
if let Some(root) = self.scopes.get_mut(0) {
root.add_function(name, min_arity, max_arity, span);
}
}
pub fn add_root_function_with_types(
&mut self,
name: String,
min_arity: usize,
max_arity: usize,
span: Span,
param_types: Option<Vec<Type>>,
return_type: Option<Type>,
) {
if let Some(root) = self.scopes.get_mut(0) {
root.add_function_with_types(
name,
min_arity,
max_arity,
span,
param_types,
return_type,
);
}
}
pub fn set_root_function_meta(&mut self, name: String, meta: SigMeta) {
self.root_function_meta.insert(name, meta);
}
pub fn set_root_global_meta(&mut self, name: String, meta: SigMeta) {
self.root_global_meta.insert(name, meta);
}
#[must_use]
pub fn get_root_function_meta(&self, name: &str) -> Option<&SigMeta> {
self.root_function_meta.get(name)
}
#[must_use]
pub fn get_root_global_meta(&self, name: &str) -> Option<&SigMeta> {
self.root_global_meta.get(name)
}
pub fn add_root_class(&mut self, name: String, span: Span) {
if let Some(root) = self.scopes.get_mut(0) {
root.add_class(name, span);
}
}
pub fn add_class_field(
&mut self,
class_name: &str,
field_name: String,
ty: Type,
visibility: MemberVisibility,
) {
self.class_members
.entry(class_name.to_string())
.or_default()
.fields
.insert(field_name, (ty, visibility));
}
pub fn add_class_method(
&mut self,
class_name: &str,
method_name: String,
param_types: Vec<Type>,
return_type: Type,
visibility: MemberVisibility,
) {
self.class_members
.entry(class_name.to_string())
.or_default()
.methods
.insert(method_name, (param_types, return_type, visibility));
}
pub fn add_class_static_field(
&mut self,
class_name: &str,
field_name: String,
ty: Type,
visibility: MemberVisibility,
) {
self.class_members
.entry(class_name.to_string())
.or_default()
.static_fields
.insert(field_name, (ty, visibility));
}
pub fn add_class_static_method(
&mut self,
class_name: &str,
method_name: String,
param_types: Vec<Type>,
return_type: Type,
visibility: MemberVisibility,
) {
self.class_members
.entry(class_name.to_string())
.or_default()
.static_methods
.insert(method_name, (param_types, return_type, visibility));
}
#[must_use]
pub fn get_class_member_type(&self, class_name: &str, member_name: &str) -> Option<Type> {
let members = self.class_members.get(class_name)?;
if let Some((ty, _)) = members.fields.get(member_name) {
return Some(ty.clone());
}
if let Some((params, ret, _)) = members.methods.get(member_name) {
return Some(Type::function(params.clone(), ret.clone()));
}
None
}
#[must_use]
pub fn get_class_static_member_type(
&self,
class_name: &str,
member_name: &str,
) -> Option<Type> {
let members = self.class_members.get(class_name)?;
if let Some((ty, _)) = members.static_fields.get(member_name) {
return Some(ty.clone());
}
if let Some((params, ret, _)) = members.static_methods.get(member_name) {
return Some(Type::function(params.clone(), ret.clone()));
}
None
}
#[must_use]
pub fn get_class_members(&self, class_name: &str) -> Option<&ClassMembers> {
self.class_members.get(class_name)
}
#[must_use]
pub fn get_function_type_as_value(&self, current: ScopeId, name: &str) -> Option<Type> {
let mut id = Some(current);
while let Some(scope_id) = id {
if let Some(scope) = self.get(scope_id) {
if let Some(ty) = scope.get_function_type_as_value(name) {
return Some(ty);
}
id = scope.parent;
} else {
break;
}
}
None
}
#[must_use]
pub fn root_has_class(&self, name: &str) -> bool {
self.get(ScopeId(0))
.is_some_and(|scope| scope.has_class(name))
}
#[must_use]
pub fn resolve(&self, current: ScopeId, name: &str) -> Option<ResolvedSymbol> {
let mut id = Some(current);
while let Some(scope_id) = id {
let scope = self.get(scope_id)?;
if let Some(v) = scope.get_variable(name) {
return Some(ResolvedSymbol::Variable(v.clone()));
}
if scope.has_class(name) {
return Some(ResolvedSymbol::Class(name.to_string()));
}
if scope.has_global(name) {
return Some(ResolvedSymbol::Global(name.to_string()));
}
if let Some(arity) = scope.get_function_arity(name) {
return Some(ResolvedSymbol::Function(name.to_string(), arity));
}
id = scope.parent;
}
None
}
}
#[derive(Clone, Debug)]
pub enum ResolvedSymbol {
Variable(VariableInfo),
Global(String),
Function(String, usize),
Class(String),
}