vyder_core/
environment.rs

1use std::collections::HashMap;
2
3use crate::prelude::*;
4
5pub struct Environment {
6    scopes: Vec<HashMap<String, (ValueResult, bool)>>,
7}
8
9impl Environment {
10    pub fn new() -> Self {
11        Self {
12            scopes: vec![HashMap::new()],
13        }
14    }
15
16    pub fn define(&mut self, identifier: &str, value: ValueResult, is_const: bool) {
17        self.scopes
18            .last_mut()
19            .expect("should never be able to remove the root scope")
20            .insert(identifier.to_string(), (value, is_const));
21    }
22
23    pub fn assign(&mut self, identifier: &str, value: ValueResult, span: &Span) -> Result<()> {
24        let mut scope_index = self.scopes.len() - 1;
25
26        loop {
27            let scope = self
28                .scopes
29                .get_mut(scope_index)
30                .expect("scope_index should never be invalid");
31
32            match scope.get(identifier) {
33                Some((_, false)) => {
34                    scope.insert(identifier.to_string(), (value, false));
35                    return Ok(());
36                }
37                Some((_, true)) => {
38                    return Err(Error::new(
39                        ErrorType::CannotAssignToConstant(identifier.to_string()),
40                        span.clone(),
41                    ))
42                }
43                None => {}
44            };
45
46            if scope_index == 0 {
47                return Err(Error::new(
48                    ErrorType::UndefinedVariable(identifier.to_string()),
49                    span.clone(),
50                ));
51            }
52
53            scope_index -= 1;
54        }
55    }
56
57    pub fn get(&self, identifier: &str, span: &Span) -> Result<&ValueResult> {
58        let mut scope_index = self.scopes.len() - 1;
59
60        loop {
61            let scope = self.scopes.get(scope_index).unwrap();
62
63            if let Some((value, _)) = scope.get(identifier) {
64                return Ok(value);
65            }
66
67            if scope_index == 0 {
68                return Err(Error::new(
69                    ErrorType::UndefinedVariable(identifier.to_string()),
70                    span.clone(),
71                ));
72            }
73
74            scope_index -= 1;
75        }
76    }
77
78    pub fn enter_scope(&mut self) {
79        self.scopes.push(HashMap::new());
80    }
81
82    pub fn exit_scope(&mut self) {
83        assert!(self.scopes.len() > 1, "tried to remove the root scope");
84
85        self.scopes.pop();
86    }
87}
88
89impl Default for Environment {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn environments_test() {
101        let mut environment = Environment::default();
102
103        environment.define(
104            "foo",
105            ValueResult::Ok(Value::new_value(
106                values::Bool { value: true }.into(),
107                Span::new((1, 1, 0), "test"),
108            )),
109            false,
110        );
111
112        assert!(environment
113            .get("bar", &Span::new((1, 1, 0), "test"))
114            .is_err());
115
116        environment.enter_scope();
117
118        environment.define(
119            "foo",
120            ValueResult::Ok(Value::new_value(
121                values::Bool { value: false }.into(),
122                Span::new((1, 1, 0), "test"),
123            )),
124            false,
125        );
126
127        assert!(
128            !environment
129                .get("foo", &Span::new((1, 1, 0), "test"))
130                .unwrap()
131                .ignore_error()
132                .expect::<values::Bool>()
133                .unwrap()
134                .value
135        );
136
137        environment.exit_scope();
138
139        assert!(
140            environment
141                .get("foo", &Span::new((1, 1, 0), "test"))
142                .unwrap()
143                .ignore_error()
144                .expect::<values::Bool>()
145                .unwrap()
146                .value
147        );
148    }
149}