Skip to main content

rl_lang/interpreter/
scopes.rs

1//! Environment stack operations - scope management and slot-indexed value access.
2//!
3//! The environment is a `Vec<Vec<EnvironmentItem>>` - a stack of frames.
4//! Each frame is a flat vec of slots indexed by the resolver's slot numbers.
5//! Depth 0 = current frame, depth 1 = one scope up, etc.
6//!
7//! Globals live in a separate `self.globals` field, addressed when `depth`
8//! points past the end of the local `environment` stack. This keeps globals
9//! from ever needing to be cloned in/out on function calls.
10
11use crate::{
12    ast::statements::TypeAnnotation,
13    interpreter::{
14        evaluator::{EnvironmentItem, Evaluator, PItem},
15        values::Value,
16    },
17    utils::{errors::Error, span::Span},
18};
19
20impl Evaluator {
21    /// Pushes a new empty scope frame onto the scope pool stack.
22    pub fn push_scope(&mut self) {
23        let frame = self.scope_pool.pop().unwrap_or_default();
24        self.environment.push(frame);
25    }
26
27    /// Pops the innermost scope frame.
28    pub fn pop_scope(&mut self) {
29        if let Some(mut frame) = self.environment.pop() {
30            frame.clear();
31            self.scope_pool.push(frame);
32        }
33    }
34
35    fn ensure_slot(frame: &mut Vec<EnvironmentItem>, slot: usize, item: EnvironmentItem) {
36        match slot.cmp(&frame.len()) {
37            std::cmp::Ordering::Less => frame[slot] = item,
38            std::cmp::Ordering::Equal => frame.push(item),
39            std::cmp::Ordering::Greater => {
40                frame.resize_with(slot, || {
41                    EnvironmentItem::PItem(PItem {
42                        value: Value::Null,
43                        type_annotation: TypeAnnotation::Null,
44                        is_const: false,
45                    })
46                });
47                frame.push(item);
48            }
49        }
50    }
51
52    /// Reads the value at `(depth, slot)` in the environment.
53    ///
54    /// `depth` counts from the innermost frame outward (0 = current).
55    /// `depth >= environment.len()` means the target is the global scope.
56    pub fn get_value(&self, depth: usize, slot: usize, span: Span) -> Result<Value, Error> {
57        if depth >= self.environment.len() {
58            return match self.globals.get(slot) {
59                Some(EnvironmentItem::PItem(p)) => Ok(p.value.clone()),
60                None => Err(self.err(format!("undefined variable at ({}, {})", depth, slot), span)),
61            };
62        }
63        let idx = self.environment.len() - 1 - depth;
64        match self.environment.get(idx).and_then(|f| f.get(slot)) {
65            Some(EnvironmentItem::PItem(p)) => Ok(p.value.clone()),
66            None => Err(self.err(format!("undefined variable at ({}, {})", depth, slot), span)),
67        }
68    }
69
70    /// Inserts a mutable value at `slot` in the current (innermost) frame.
71    ///
72    /// Grows the frame with `Null` placeholders if `slot` is past the end.
73    pub fn insert_value(
74        &mut self,
75        slot: usize,
76        value: Value,
77        type_annotation: TypeAnnotation,
78        _: Span,
79    ) -> Result<(), Error> {
80        if self.environment.is_empty() {
81            Self::ensure_slot(
82                &mut self.globals,
83                slot,
84                EnvironmentItem::PItem(PItem {
85                    value,
86                    type_annotation,
87                    is_const: false,
88                }),
89            );
90            return Ok(());
91        }
92
93        let frame = self
94            .environment
95            .last_mut()
96            .expect("checked non-empty above");
97
98        Self::ensure_slot(
99            frame,
100            slot,
101            EnvironmentItem::PItem(PItem {
102                value,
103                type_annotation,
104                is_const: false,
105            }),
106        );
107        Ok(())
108    }
109
110    /// Inserts a mutable value at `slot` in the global scope.
111    ///
112    /// Used for top-level `dec` declarations, which live in `self.globals`
113    /// rather than the local `environment` stack.
114    pub fn insert_global(&mut self, slot: usize, value: Value, type_annotation: TypeAnnotation) {
115        Self::ensure_slot(
116            &mut self.globals,
117            slot,
118            EnvironmentItem::PItem(PItem {
119                value,
120                type_annotation,
121                is_const: false,
122            }),
123        );
124    }
125
126    /// Inserts an immutable (const) value at `slot` in the current frame.
127    ///
128    /// Returns an error if the slot already holds a const.
129    pub fn insert_const(
130        &mut self,
131        slot: usize,
132        value: Value,
133        type_annotation: TypeAnnotation,
134        span: Span,
135    ) -> Result<(), Error> {
136        if self.environment.is_empty() {
137            Self::ensure_slot(
138                &mut self.globals,
139                slot,
140                EnvironmentItem::PItem(PItem {
141                    value,
142                    type_annotation,
143                    is_const: true,
144                }),
145            );
146            return Ok(());
147        }
148
149        if let Some(EnvironmentItem::PItem(p)) = self.environment.last().and_then(|f| f.get(slot))
150            && p.is_const
151        {
152            return Err(self.err(format!("slot {} is already a constant", slot), span));
153        }
154
155        let frame = self
156            .environment
157            .last_mut()
158            .expect("checked non-empty above");
159
160        Self::ensure_slot(
161            frame,
162            slot,
163            EnvironmentItem::PItem(PItem {
164                value,
165                type_annotation,
166                is_const: true,
167            }),
168        );
169        Ok(())
170    }
171
172    /// Inserts an immutable (const) value at `slot` in the global scope.
173    ///
174    /// Used for top-level `const` declarations. Returns an error if the slot
175    /// already holds a const.
176    pub fn insert_const_global(
177        &mut self,
178        slot: usize,
179        value: Value,
180        type_annotation: TypeAnnotation,
181        span: Span,
182    ) -> Result<(), Error> {
183        if let Some(EnvironmentItem::PItem(p)) = self.globals.get(slot)
184            && p.is_const
185        {
186            return Err(self.err(format!("slot {} is already a constant", slot), span));
187        }
188        Self::ensure_slot(
189            &mut self.globals,
190            slot,
191            EnvironmentItem::PItem(PItem {
192                value,
193                type_annotation,
194                is_const: true,
195            }),
196        );
197        Ok(())
198    }
199
200    /// Overwrites the value at `(depth, slot)`, enforcing type compatibility and immutability.
201    ///
202    /// `depth >= environment.len()` means the target is the global scope.
203    pub fn assign_value(
204        &mut self,
205        depth: usize,
206        slot: usize,
207        value: Value,
208        value_type: TypeAnnotation,
209        span: Span,
210    ) -> Result<(), Error> {
211        if depth >= self.environment.len() {
212            if self.globals.get(slot).is_none() {
213                return Err(self.err(format!("undefined slot {} at depth {}", slot, depth), span));
214            }
215            let entry = &mut self.globals[slot];
216            return match entry {
217                EnvironmentItem::PItem(p) => {
218                    if p.is_const {
219                        return Err(self.err("cannot assign to constant", span));
220                    }
221                    let declared = p.type_annotation.clone();
222                    let types_match = matches!(value, Value::Null)
223                        || match (&declared, &value_type) {
224                            (TypeAnnotation::Array(_), TypeAnnotation::Array(inner))
225                                if **inner == TypeAnnotation::Null =>
226                            {
227                                true
228                            }
229                            (TypeAnnotation::Array(a), TypeAnnotation::Array(_))
230                                if **a == TypeAnnotation::Null =>
231                            {
232                                true
233                            }
234                            _ => Evaluator::types_compatible(&value_type, &declared),
235                        };
236                    if !types_match {
237                        return Err(self.err(
238                            format!(
239                                "type mismatch: cannot assign {:?} to {:?}",
240                                value_type, declared
241                            ),
242                            span,
243                        ));
244                    }
245                    p.value = value;
246                    Ok(())
247                }
248            };
249        }
250
251        let idx = self.environment.len() - 1 - depth;
252        if slot >= self.environment[idx].len() {
253            return Err(self.err(format!("undefined slot {} at depth {}", slot, depth), span));
254        }
255        let entry = &mut self.environment[idx][slot];
256        match entry {
257            EnvironmentItem::PItem(p) => {
258                if p.is_const {
259                    return Err(self.err("cannot assign to constant", span));
260                }
261                let declared = p.type_annotation.clone();
262                let types_match = matches!(value, Value::Null)
263                    || match (&declared, &value_type) {
264                        (TypeAnnotation::Array(_), TypeAnnotation::Array(inner))
265                            if **inner == TypeAnnotation::Null =>
266                        {
267                            true
268                        }
269                        (TypeAnnotation::Array(a), TypeAnnotation::Array(_))
270                            if **a == TypeAnnotation::Null =>
271                        {
272                            true
273                        }
274                        _ => Evaluator::types_compatible(&value_type, &declared),
275                    };
276                if !types_match {
277                    return Err(self.err(
278                        format!(
279                            "type mismatch: cannot assign {:?} to {:?}",
280                            value_type, declared
281                        ),
282                        span,
283                    ));
284                }
285                p.value = value;
286                Ok(())
287            }
288        }
289    }
290
291    /// Looks up a variable by name via the resolver - used only in tests.
292    pub fn get_value_raw(&self, name: &str) -> Option<Value> {
293        let (depth, slot) = self.resolver.resolve_name(name)?;
294        if depth >= self.environment.len() {
295            return match self.globals.get(slot)? {
296                crate::interpreter::evaluator::EnvironmentItem::PItem(p) => Some(p.value.clone()),
297            };
298        }
299        let idx = self.environment.len() - 1 - depth;
300        match self.environment.get(idx)?.get(slot)? {
301            crate::interpreter::evaluator::EnvironmentItem::PItem(p) => Some(p.value.clone()),
302        }
303    }
304
305    /// Returns the declared [`TypeAnnotation`] of the slot at `(depth, slot)`, if it exists.
306    pub fn get_declared_type(&self, depth: usize, slot: usize) -> Option<TypeAnnotation> {
307        if depth >= self.environment.len() {
308            return match self.globals.get(slot)? {
309                EnvironmentItem::PItem(p) => Some(p.type_annotation.clone()),
310            };
311        }
312        let idx = self.environment.len() - 1 - depth;
313        match self.environment.get(idx)?.get(slot)? {
314            EnvironmentItem::PItem(p) => Some(p.type_annotation.clone()),
315        }
316    }
317}