use std::collections::{HashMap, HashSet};
use crate::ast::{celled_locals, child_funcdefs, free_names, Params, Stmt};
use crate::builtins::BUILTINS;
use crate::modules::{Program, ProgramFile};
use crate::stdlib::Module;
use crate::token::Span;
pub(super) struct FileTable {
pub(super) funcs: HashMap<String, Params>,
pub(super) consts: HashSet<String>,
pub(super) members: Vec<String>,
pub(super) stdlib_imports: HashMap<String, &'static Module>,
pub(super) user_imports: HashMap<String, u32>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum FnKind {
TopLevel,
Closure,
Method,
}
pub(super) struct FnInfo {
pub(super) file_id: u32,
pub(super) fn_id: Option<u32>,
pub(super) name: String,
pub(super) params: Vec<String>,
pub(super) body: Vec<Stmt>,
pub(super) captures: Vec<String>,
pub(super) cell_names: HashSet<String>,
pub(super) kind: FnKind,
}
pub(super) struct FnInfoView {
pub(super) name: String,
pub(super) params: Vec<String>,
pub(super) body: Vec<Stmt>,
pub(super) captures: Vec<String>,
pub(super) cell_names: HashSet<String>,
}
pub(super) enum ArmSpec {
TopFunc {
file_id: u32,
name: String,
params: Params,
},
Closure {
name: String,
id: u32,
params: Params,
captures: usize,
},
Builtin { name: &'static str },
Module {
name: String,
runtime_fn: &'static str,
arity: usize,
},
Ctor { file_id: u32, name: String },
}
pub(super) struct Analysis {
pub(super) fn_info: HashMap<(u32, Span), FnInfo>,
pub(super) registry: Vec<ArmSpec>,
pub(super) top_func_ids: HashMap<(u32, String), u32>,
pub(super) builtin_ids: HashMap<&'static str, u32>,
pub(super) module_fn_ids: HashMap<(String, String), u32>,
pub(super) ctor_ids: HashMap<(u32, String), u32>,
}
pub(super) fn file_table(file: &ProgramFile) -> FileTable {
let mut funcs = HashMap::new();
let mut consts = HashSet::new();
let mut func_members = Vec::new();
let mut class_members = Vec::new();
let mut const_members = Vec::new();
for stmt in &file.script.stmts {
match stmt {
Stmt::FuncDef { name, params, .. } => {
funcs.insert(name.clone(), params.clone());
func_members.push(name.clone());
}
Stmt::ObjDef { name, .. } => {
class_members.push(name.clone());
}
Stmt::ConstDecl { name, .. } => {
consts.insert(name.clone());
const_members.push(name.clone());
}
_ => {}
}
}
func_members.extend(class_members);
func_members.extend(const_members);
FileTable {
funcs,
consts,
members: func_members,
stdlib_imports: file.stdlib_imports.iter().cloned().collect(),
user_imports: file.user_imports.iter().cloned().collect(),
}
}
struct Analyzer {
fn_info: HashMap<(u32, Span), FnInfo>,
registry: Vec<ArmSpec>,
top_func_ids: HashMap<(u32, String), u32>,
next_id: u32,
}
impl Analyzer {
#[allow(clippy::too_many_arguments)]
fn analyze(
&mut self,
file_id: u32,
name: &str,
params: &Params,
body: &[Stmt],
span: Span,
kind: FnKind,
enclosing_cells: &HashSet<String>,
) {
let mut binding = Vec::new();
if kind == FnKind::Method {
binding.push("self".to_string());
}
binding.extend(params.binding_names());
let free = free_names(&binding, body);
let mut captures: Vec<String> = free
.iter()
.filter(|n| enclosing_cells.contains(*n))
.cloned()
.collect();
captures.sort();
let mut cell_names = celled_locals(&binding, body);
cell_names.extend(captures.iter().cloned());
let fn_id = match kind {
FnKind::Method => None,
FnKind::TopLevel => {
let id = self.next_id;
self.next_id += 1;
self.registry.push(ArmSpec::TopFunc {
file_id,
name: name.to_string(),
params: params.clone(),
});
self.top_func_ids.insert((file_id, name.to_string()), id);
Some(id)
}
FnKind::Closure => {
let id = self.next_id;
self.next_id += 1;
self.registry.push(ArmSpec::Closure {
name: name.to_string(),
id,
params: params.clone(),
captures: captures.len(),
});
Some(id)
}
};
self.fn_info.insert(
(file_id, span),
FnInfo {
file_id,
fn_id,
name: name.to_string(),
params: binding,
body: body.to_vec(),
captures,
cell_names: cell_names.clone(),
kind,
},
);
for (child_name, child_params, child_body, child_span) in child_funcdefs(body) {
self.analyze(
file_id,
child_name,
child_params,
child_body,
child_span,
FnKind::Closure,
&cell_names,
);
}
}
fn analyze_file(&mut self, file: &ProgramFile) {
let file_id = file.file_id;
let empty = HashSet::new();
for stmt in &file.script.stmts {
if let Stmt::FuncDef {
name,
params,
body,
span,
} = stmt
{
self.analyze(file_id, name, params, body, *span, FnKind::TopLevel, &empty);
}
}
for stmt in &file.script.stmts {
if let Stmt::ObjDef { methods, .. } = stmt {
for method in methods {
if let Stmt::FuncDef {
name,
params,
body,
span,
} = method
{
self.analyze(file_id, name, params, body, *span, FnKind::Method, &empty);
}
}
}
}
for stmt in &file.script.stmts {
if matches!(
stmt,
Stmt::If { .. } | Stmt::For { .. } | Stmt::While { .. } | Stmt::Try { .. }
) {
for (name, params, body, span) in child_funcdefs(std::slice::from_ref(stmt)) {
self.analyze(file_id, name, params, body, span, FnKind::Closure, &empty);
}
}
}
}
}
pub(super) fn analyze_program(program: &Program) -> Analysis {
let mut analyzer = Analyzer {
fn_info: HashMap::new(),
registry: Vec::new(),
top_func_ids: HashMap::new(),
next_id: 0,
};
for file in &program.files {
analyzer.analyze_file(file);
}
let mut builtin_ids = HashMap::new();
for builtin in BUILTINS {
let id = analyzer.next_id;
analyzer.next_id += 1;
analyzer
.registry
.push(ArmSpec::Builtin { name: builtin.name });
builtin_ids.insert(builtin.name, id);
}
let mut module_fn_ids = HashMap::new();
for file in &program.files {
for (module_name, module) in &file.stdlib_imports {
if module_fn_ids
.keys()
.any(|(m, _): &(String, String)| m == module_name)
{
continue;
}
for func in module.funcs {
let id = analyzer.next_id;
analyzer.next_id += 1;
analyzer.registry.push(ArmSpec::Module {
name: format!("{module_name}.{}", func.name),
runtime_fn: func.runtime_fn,
arity: func.arity,
});
module_fn_ids.insert((module_name.clone(), func.name.to_string()), id);
}
}
}
let mut ctor_ids = HashMap::new();
for file in &program.files {
for stmt in &file.script.stmts {
if let Stmt::ObjDef { name, .. } = stmt {
let id = analyzer.next_id;
analyzer.next_id += 1;
analyzer.registry.push(ArmSpec::Ctor {
file_id: file.file_id,
name: name.clone(),
});
ctor_ids.insert((file.file_id, name.clone()), id);
}
}
}
Analysis {
fn_info: analyzer.fn_info,
registry: analyzer.registry,
top_func_ids: analyzer.top_func_ids,
builtin_ids,
module_fn_ids,
ctor_ids,
}
}