1use std::collections::BTreeMap;
2use std::rc::Rc;
3use std::{cell::RefCell, path::PathBuf};
4
5use crate::chunk::CompiledFunctionRef;
6
7use super::{VmError, VmValue};
8
9#[derive(Debug, Clone)]
11pub struct VmClosure {
12 pub func: CompiledFunctionRef,
13 pub env: VmEnv,
14 pub source_dir: Option<PathBuf>,
18 pub module_functions: Option<ModuleFunctionRegistry>,
22 pub module_state: Option<ModuleState>,
35}
36
37pub type ModuleFunctionRegistry = Rc<RefCell<BTreeMap<String, Rc<VmClosure>>>>;
38pub type ModuleState = Rc<RefCell<VmEnv>>;
39
40#[derive(Debug, Clone)]
42pub struct VmEnv {
43 pub(crate) scopes: Vec<Scope>,
44}
45
46#[derive(Debug, Clone)]
47pub(crate) struct Scope {
48 pub(crate) vars: BTreeMap<String, (VmValue, bool)>, }
50
51impl Default for VmEnv {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl VmEnv {
58 pub fn new() -> Self {
59 Self {
60 scopes: vec![Scope {
61 vars: BTreeMap::new(),
62 }],
63 }
64 }
65
66 pub fn push_scope(&mut self) {
67 self.scopes.push(Scope {
68 vars: BTreeMap::new(),
69 });
70 }
71
72 pub fn pop_scope(&mut self) {
73 if self.scopes.len() > 1 {
74 self.scopes.pop();
75 }
76 }
77
78 pub fn scope_depth(&self) -> usize {
79 self.scopes.len()
80 }
81
82 pub fn truncate_scopes(&mut self, target_depth: usize) {
83 let min_depth = target_depth.max(1);
84 while self.scopes.len() > min_depth {
85 self.scopes.pop();
86 }
87 }
88
89 pub fn get(&self, name: &str) -> Option<VmValue> {
90 for scope in self.scopes.iter().rev() {
91 if let Some((val, _)) = scope.vars.get(name) {
92 return Some(val.clone());
93 }
94 }
95 None
96 }
97
98 pub(crate) fn contains(&self, name: &str) -> bool {
99 self.scopes
100 .iter()
101 .rev()
102 .any(|scope| scope.vars.contains_key(name))
103 }
104
105 pub fn define(&mut self, name: &str, value: VmValue, mutable: bool) -> Result<(), VmError> {
106 if let Some(scope) = self.scopes.last_mut() {
107 if let Some((_, existing_mutable)) = scope.vars.get(name) {
108 if !existing_mutable && !mutable {
109 return Err(VmError::Runtime(format!(
110 "Cannot redeclare immutable variable '{name}' in the same scope (use 'var' for mutable bindings)"
111 )));
112 }
113 }
114 scope.vars.insert(name.to_string(), (value, mutable));
115 }
116 Ok(())
117 }
118
119 pub fn all_variables(&self) -> BTreeMap<String, VmValue> {
120 let mut vars = BTreeMap::new();
121 for scope in &self.scopes {
122 for (name, (value, _)) in &scope.vars {
123 vars.insert(name.clone(), value.clone());
124 }
125 }
126 vars
127 }
128
129 pub fn assign(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
130 for scope in self.scopes.iter_mut().rev() {
131 if let Some((_, mutable)) = scope.vars.get(name) {
132 if !mutable {
133 return Err(VmError::ImmutableAssignment(name.to_string()));
134 }
135 scope.vars.insert(name.to_string(), (value, true));
136 return Ok(());
137 }
138 }
139 Err(VmError::UndefinedVariable(name.to_string()))
140 }
141
142 pub fn assign_debug(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
150 for scope in self.scopes.iter_mut().rev() {
151 if let Some((_, mutable)) = scope.vars.get(name) {
152 let mutable = *mutable;
153 scope.vars.insert(name.to_string(), (value, mutable));
154 return Ok(());
155 }
156 }
157 Err(VmError::UndefinedVariable(name.to_string()))
158 }
159}
160
161fn levenshtein(a: &str, b: &str) -> usize {
163 let a: Vec<char> = a.chars().collect();
164 let b: Vec<char> = b.chars().collect();
165 let (m, n) = (a.len(), b.len());
166 let mut prev = (0..=n).collect::<Vec<_>>();
167 let mut curr = vec![0; n + 1];
168 for i in 1..=m {
169 curr[0] = i;
170 for j in 1..=n {
171 let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
172 curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
173 }
174 std::mem::swap(&mut prev, &mut curr);
175 }
176 prev[n]
177}
178
179pub fn closest_match<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<String> {
182 let max_dist = match name.len() {
183 0..=2 => 1,
184 3..=5 => 2,
185 _ => 3,
186 };
187 candidates
188 .filter(|c| *c != name && !c.starts_with("__"))
189 .map(|c| (c, levenshtein(name, c)))
190 .filter(|(_, d)| *d <= max_dist)
191 .min_by(|(a, da), (b, db)| {
193 da.cmp(db)
194 .then_with(|| {
195 let a_diff = (a.len() as isize - name.len() as isize).unsigned_abs();
196 let b_diff = (b.len() as isize - name.len() as isize).unsigned_abs();
197 a_diff.cmp(&b_diff)
198 })
199 .then_with(|| a.cmp(b))
200 })
201 .map(|(c, _)| c.to_string())
202}