Skip to main content

brooks_lib/environment/
scope.rs

1// brooks, Copyright 2026, Will Hawkins
2//
3// This file is part of brooks.
4
5// This file is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18use std::{collections::HashMap, fmt::Debug, ops::Add};
19
20#[derive(Debug, Clone, Default)]
21pub struct Scope<I: Clone + Default> {
22    pub items: HashMap<String, I>,
23}
24
25impl<I: Clone + Default> Scope<I> {
26    pub fn lookup(&self, id: &str) -> Option<I> {
27        self.items.get(id).cloned()
28    }
29    pub fn insert(&self, id: &str, value: I) -> Self {
30        let mut next = self.items.clone();
31        next.insert(id.to_string(), value);
32        Self { items: next }
33    }
34}
35
36impl<I: Clone + Default> Add for &Scope<I> {
37    type Output = Scope<I>;
38
39    fn add(self, rhs: Self) -> Self::Output {
40        let mut ns = Scope::<I> {
41            items: HashMap::new(),
42        };
43        for (k, v) in &self.items {
44            ns = ns.insert(k, v.clone());
45        }
46        for (k, v) in &rhs.items {
47            ns = ns.insert(k, v.clone());
48        }
49        ns
50    }
51}
52
53#[derive(Debug, Clone)]
54pub struct Scopes<I: Clone + Default> {
55    pub scopes: Vec<Scope<I>>,
56}
57
58impl<I: Clone + Default> From<Scope<I>> for Scopes<I> {
59    fn from(value: Scope<I>) -> Self {
60        Scopes {
61            scopes: vec![value],
62        }
63    }
64}
65
66impl<I: Clone + Default> Scopes<I> {
67    pub fn lookup(&self, id: &str) -> Option<I> {
68        for scope in self.scopes.iter().rev() {
69            if let Some(id) = scope.lookup(id) {
70                return Some(id);
71            }
72        }
73        None
74    }
75
76    pub fn insert(&self, id: &str, value: I) -> Self {
77        let updated_scope = self.scopes[0].insert(id, value);
78
79        let mut next = self.scopes.clone();
80        next[0] = updated_scope;
81
82        Self { scopes: next }
83    }
84
85    pub fn leave(&self) -> Scopes<I> {
86        let mut next = self.scopes.clone();
87        next.pop();
88        Self { scopes: next }
89    }
90
91    pub fn enter(&self) -> Scopes<I> {
92        let mut next = self.scopes.clone();
93        next.push(Scope::default());
94        Self { scopes: next }
95    }
96
97    pub fn enter_scope(&self, entering: Scope<I>) -> Scopes<I> {
98        let mut next = self.scopes.clone();
99        next.push(entering);
100        Self { scopes: next }
101    }
102
103    pub fn current(&self) -> &Scope<I> {
104        &self.scopes[0]
105    }
106}
107
108impl<I: Clone + Default> Default for Scopes<I> {
109    fn default() -> Self {
110        Self {
111            scopes: vec![Scope::default()],
112        }
113    }
114}
115
116#[cfg(test)]
117mod scope_tests {
118    use std::assert_matches;
119    use std::collections::HashMap;
120
121    use crate::environment::scope::Scope;
122
123    #[test]
124    fn test_operator_plus() {
125        let mut s1 = Scope::<i8> {
126            items: HashMap::new(),
127        };
128        s1 = s1.insert("x", 5);
129        let mut s2 = Scope::<i8> {
130            items: HashMap::new(),
131        };
132        s2 = s2.insert("y", 4);
133
134        let s3 = &s1 + &s2;
135
136        assert_matches!(s3.lookup("y"), Some(4));
137        assert_matches!(s3.lookup("x"), Some(5));
138    }
139}