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)]
53pub struct VmEnv {
54 pub(crate) scopes: Vec<Scope>,
55}
56
57#[derive(Debug, Clone)]
58pub(crate) struct Scope {
59 pub(crate) vars: Rc<BTreeMap<String, (VmValue, bool)>>, }
61
62impl Scope {
63 #[inline]
64 fn empty() -> Self {
65 Self {
66 vars: Rc::new(BTreeMap::new()),
67 }
68 }
69}
70
71impl Default for VmEnv {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77impl VmEnv {
78 pub fn new() -> Self {
79 Self {
80 scopes: vec![Scope::empty()],
81 }
82 }
83
84 pub fn push_scope(&mut self) {
85 self.scopes.push(Scope::empty());
86 }
87
88 pub fn pop_scope(&mut self) {
89 if self.scopes.len() > 1 {
90 self.scopes.pop();
91 }
92 }
93
94 pub fn scope_depth(&self) -> usize {
95 self.scopes.len()
96 }
97
98 pub fn truncate_scopes(&mut self, target_depth: usize) {
99 let min_depth = target_depth.max(1);
100 while self.scopes.len() > min_depth {
101 self.scopes.pop();
102 }
103 }
104
105 pub fn get(&self, name: &str) -> Option<VmValue> {
106 for scope in self.scopes.iter().rev() {
107 if let Some((val, _)) = scope.vars.get(name) {
108 return Some(val.clone());
109 }
110 }
111 None
112 }
113
114 pub(crate) fn contains(&self, name: &str) -> bool {
115 self.scopes
116 .iter()
117 .rev()
118 .any(|scope| scope.vars.contains_key(name))
119 }
120
121 pub fn define(&mut self, name: &str, value: VmValue, mutable: bool) -> Result<(), VmError> {
122 if let Some(scope) = self.scopes.last_mut() {
123 if let Some((_, existing_mutable)) = scope.vars.get(name) {
124 if !existing_mutable && !mutable {
125 return Err(VmError::Runtime(format!(
126 "Cannot redeclare immutable variable '{name}' in the same scope (use 'var' for mutable bindings)"
127 )));
128 }
129 }
130 Rc::make_mut(&mut scope.vars).insert(name.to_string(), (value, mutable));
131 }
132 Ok(())
133 }
134
135 pub fn all_variables(&self) -> BTreeMap<String, VmValue> {
136 let mut vars = BTreeMap::new();
137 for scope in &self.scopes {
138 for (name, (value, _)) in scope.vars.iter() {
139 vars.insert(name.clone(), value.clone());
140 }
141 }
142 vars
143 }
144
145 pub fn assign(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
146 for scope in self.scopes.iter_mut().rev() {
147 if let Some((_, mutable)) = scope.vars.get(name) {
148 if !mutable {
149 return Err(VmError::ImmutableAssignment(name.to_string()));
150 }
151 Rc::make_mut(&mut scope.vars).insert(name.to_string(), (value, true));
152 return Ok(());
153 }
154 }
155 Err(VmError::UndefinedVariable(name.to_string()))
156 }
157
158 pub fn assign_debug(&mut self, name: &str, value: VmValue) -> Result<(), VmError> {
166 for scope in self.scopes.iter_mut().rev() {
167 if let Some((_, mutable)) = scope.vars.get(name) {
168 let mutable = *mutable;
169 Rc::make_mut(&mut scope.vars).insert(name.to_string(), (value, mutable));
170 return Ok(());
171 }
172 }
173 Err(VmError::UndefinedVariable(name.to_string()))
174 }
175}
176
177fn levenshtein(a: &str, b: &str) -> usize {
179 let a: Vec<char> = a.chars().collect();
180 let b: Vec<char> = b.chars().collect();
181 let (m, n) = (a.len(), b.len());
182 let mut prev = (0..=n).collect::<Vec<_>>();
183 let mut curr = vec![0; n + 1];
184 for i in 1..=m {
185 curr[0] = i;
186 for j in 1..=n {
187 let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
188 curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
189 }
190 std::mem::swap(&mut prev, &mut curr);
191 }
192 prev[n]
193}
194
195pub fn closest_match<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<String> {
198 let max_dist = match name.len() {
199 0..=2 => 1,
200 3..=5 => 2,
201 _ => 3,
202 };
203 candidates
204 .filter(|c| *c != name && !c.starts_with("__"))
205 .map(|c| (c, levenshtein(name, c)))
206 .filter(|(_, d)| *d <= max_dist)
207 .min_by(|(a, da), (b, db)| {
209 da.cmp(db)
210 .then_with(|| {
211 let a_diff = (a.len() as isize - name.len() as isize).unsigned_abs();
212 let b_diff = (b.len() as isize - name.len() as isize).unsigned_abs();
213 a_diff.cmp(&b_diff)
214 })
215 .then_with(|| a.cmp(b))
216 })
217 .map(|(c, _)| c.to_string())
218}