use std::collections::BTreeMap;
use std::rc::Rc;
use std::{cell::RefCell, path::PathBuf};
use crate::chunk::CompiledFunctionRef;
use super::{VmError, VmValue};
#[derive(Debug, Clone)]
pub struct VmClosure {
pub func: CompiledFunctionRef,
pub env: VmEnv,
pub source_dir: Option<PathBuf>,
pub module_functions: Option<ModuleFunctionRegistry>,
pub module_state: Option<ModuleState>,
}
pub type ModuleFunctionRegistry = Rc<RefCell<BTreeMap<String, Rc<VmClosure>>>>;
pub type ModuleState = Rc<RefCell<VmEnv>>;
#[derive(Debug, Clone)]
pub struct VmEnv {
pub(crate) scopes: Vec<Scope>,
}
#[derive(Debug, Clone)]
pub(crate) struct Scope {
pub(crate) vars: BTreeMap<String, (VmValue, bool)>, }
impl Default for VmEnv {
fn default() -> Self {
Self::new()
}
}
impl VmEnv {
pub fn new() -> Self {
Self {
scopes: vec![Scope {
vars: BTreeMap::new(),
}],
}
}
pub fn push_scope(&mut self) {
self.scopes.push(Scope {
vars: BTreeMap::new(),
});
}
pub fn pop_scope(&mut self) {
if self.scopes.len() > 1 {
self.scopes.pop();
}
}
pub fn scope_depth(&self) -> usize {
self.scopes.len()
}
pub fn truncate_scopes(&mut self, target_depth: usize) {
let min_depth = target_depth.max(1);
while self.scopes.len() > min_depth {
self.scopes.pop();
}
}
pub fn get(&self, name: &str) -> Option<VmValue> {
for scope in self.scopes.iter().rev() {
if let Some((val, _)) = scope.vars.get(name) {
return Some(val.clone());
}
}
None
}
pub(crate) fn contains(&self, name: &str) -> bool {
self.scopes
.iter()
.rev()
.any(|scope| scope.vars.contains_key(name))
}
pub fn define(&mut self, name: &str, value: VmValue, mutable: bool) -> Result<(), VmError> {
if let Some(scope) = self.scopes.last_mut() {
if let Some((_, existing_mutable)) = scope.vars.get(name) {
if !existing_mutable && !mutable {
return Err(VmError::Runtime(format!(
"Cannot redeclare immutable variable '{name}' in the same scope (use 'var' for mutable bindings)"
)));
}
}
scope.vars.insert(name.to_string(), (value, mutable));
}
Ok(())
}
pub fn all_variables(&self) -> BTreeMap<String, VmValue> {
let mut vars = BTreeMap::new();
for scope in &self.scopes {
for (name, (value, _)) in &scope.vars {
vars.insert(name.clone(), value.clone());
}
}
vars
}
pub fn assign(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
for scope in self.scopes.iter_mut().rev() {
if let Some((_, mutable)) = scope.vars.get(name) {
if !mutable {
return Err(VmError::ImmutableAssignment(name.to_string()));
}
scope.vars.insert(name.to_string(), (value, true));
return Ok(());
}
}
Err(VmError::UndefinedVariable(name.to_string()))
}
pub fn assign_debug(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
for scope in self.scopes.iter_mut().rev() {
if let Some((_, mutable)) = scope.vars.get(name) {
let mutable = *mutable;
scope.vars.insert(name.to_string(), (value, mutable));
return Ok(());
}
}
Err(VmError::UndefinedVariable(name.to_string()))
}
}
fn levenshtein(a: &str, b: &str) -> usize {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().collect();
let (m, n) = (a.len(), b.len());
let mut prev = (0..=n).collect::<Vec<_>>();
let mut curr = vec![0; n + 1];
for i in 1..=m {
curr[0] = i;
for j in 1..=n {
let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}
pub fn closest_match<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<String> {
let max_dist = match name.len() {
0..=2 => 1,
3..=5 => 2,
_ => 3,
};
candidates
.filter(|c| *c != name && !c.starts_with("__"))
.map(|c| (c, levenshtein(name, c)))
.filter(|(_, d)| *d <= max_dist)
.min_by(|(a, da), (b, db)| {
da.cmp(db)
.then_with(|| {
let a_diff = (a.len() as isize - name.len() as isize).unsigned_abs();
let b_diff = (b.len() as isize - name.len() as isize).unsigned_abs();
a_diff.cmp(&b_diff)
})
.then_with(|| a.cmp(b))
})
.map(|(c, _)| c.to_string())
}