1use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9
10use crate::value::RexxValue;
11
12#[derive(Debug, Clone, Default)]
14pub struct ConditionInfoData {
15 pub condition: String,
16 pub description: String,
17 pub instruction: String,
18 pub status: String,
19}
20
21#[derive(Debug, Clone)]
23pub struct Environment {
24 scopes: Vec<Scope>,
26 pub condition_info: Option<ConditionInfoData>,
28 address_default: String,
30 address_previous: String,
32 source_path: Option<PathBuf>,
34}
35
36#[derive(Debug, Clone)]
38struct Scope {
39 vars: HashMap<String, RexxValue>,
41 stems: HashMap<String, StemVar>,
43 exposed: Vec<String>,
45}
46
47#[derive(Debug, Clone)]
49struct StemVar {
50 default: Option<RexxValue>,
53 entries: HashMap<String, RexxValue>,
55}
56
57impl Environment {
58 pub fn new() -> Self {
59 Self {
60 scopes: vec![Scope::new()],
61 condition_info: None,
62 address_default: "SYSTEM".to_string(),
63 address_previous: "SYSTEM".to_string(),
64 source_path: None,
65 }
66 }
67
68 pub fn get(&self, name: &str) -> RexxValue {
75 let upper = name.to_uppercase();
76 let scope = self.scopes.last().expect("environment has no scopes");
77 if let Some(val) = scope.vars.get(&upper) {
78 return val.clone();
79 }
80 RexxValue::new(upper)
82 }
83
84 pub fn set(&mut self, name: &str, value: RexxValue) {
86 let upper = name.to_uppercase();
87 self.current_scope_mut().vars.insert(upper, value);
88 }
89
90 pub fn get_compound(&self, stem: &str, resolved_tail: &str) -> RexxValue {
94 let stem_upper = format!("{}.", stem.to_uppercase());
95 let tail_upper = resolved_tail.to_uppercase();
96
97 let scope = self.scopes.last().expect("environment has no scopes");
98 if let Some(stem_var) = scope.stems.get(&stem_upper) {
99 if let Some(val) = stem_var.entries.get(&tail_upper) {
100 return val.clone();
101 }
102 if let Some(ref default) = stem_var.default {
103 return default.clone();
104 }
105 }
106 RexxValue::new(format!("{stem_upper}{tail_upper}"))
108 }
109
110 pub fn set_compound(&mut self, stem: &str, resolved_tail: &str, value: RexxValue) {
112 let stem_upper = format!("{}.", stem.to_uppercase());
113 let tail_upper = resolved_tail.to_uppercase();
114
115 let scope = self.current_scope_mut();
116 let stem_var = scope.stems.entry(stem_upper).or_insert_with(StemVar::new);
117 stem_var.entries.insert(tail_upper, value);
118 }
119
120 pub fn set_stem_default(&mut self, stem: &str, value: RexxValue) {
122 let stem_upper = format!("{}.", stem.to_uppercase());
123 let scope = self.current_scope_mut();
124 let stem_var = scope.stems.entry(stem_upper).or_insert_with(StemVar::new);
125 stem_var.default = Some(value);
126 }
127
128 pub fn drop(&mut self, name: &str) {
130 let upper = name.to_uppercase();
131 self.current_scope_mut().vars.remove(&upper);
132 }
133
134 pub fn push_procedure(&mut self) {
136 self.scopes.push(Scope::new());
137 }
138
139 pub fn push_procedure_expose(&mut self, names: &[String]) {
141 let mut new_scope = Scope::new();
142
143 let caller = self.scopes.last().expect("environment has no scopes");
144 for name in names {
145 let upper = name.to_uppercase();
146 if upper.ends_with('.') {
147 if let Some(stem_var) = caller.stems.get(&upper) {
149 new_scope.stems.insert(upper.clone(), stem_var.clone());
150 }
151 } else {
152 if let Some(val) = caller.vars.get(&upper) {
154 new_scope.vars.insert(upper.clone(), val.clone());
155 }
156 }
157 }
158
159 new_scope.exposed = names.iter().map(|n| n.to_uppercase()).collect();
160 self.scopes.push(new_scope);
161 }
162
163 pub fn pop_procedure(&mut self) {
166 debug_assert!(
167 self.scopes.len() > 1,
168 "pop_procedure called with no nested scope"
169 );
170 if self.scopes.len() > 1 {
171 let popped = self.scopes.pop().unwrap();
172 let parent = self.scopes.last_mut().unwrap();
173 for name in &popped.exposed {
174 if name.ends_with('.') {
175 if let Some(stem_var) = popped.stems.get(name) {
176 parent.stems.insert(name.clone(), stem_var.clone());
177 }
178 } else if let Some(val) = popped.vars.get(name) {
179 parent.vars.insert(name.clone(), val.clone());
180 } else {
181 parent.vars.remove(name);
183 }
184 }
185 }
186 }
187
188 pub fn address(&self) -> &str {
190 &self.address_default
191 }
192
193 pub fn set_address(&mut self, env: &str) {
195 self.address_previous = std::mem::replace(&mut self.address_default, env.to_string());
196 }
197
198 pub fn swap_address(&mut self) {
200 std::mem::swap(&mut self.address_default, &mut self.address_previous);
201 }
202
203 pub fn set_source_path(&mut self, path: PathBuf) {
205 self.source_path = Some(path);
206 }
207
208 pub fn clear_source_path(&mut self) {
210 self.source_path = None;
211 }
212
213 pub fn source_path(&self) -> Option<&Path> {
215 self.source_path.as_deref()
216 }
217
218 pub fn source_dir(&self) -> Option<&Path> {
220 self.source_path.as_deref().and_then(Path::parent)
221 }
222
223 pub fn set_condition_info(&mut self, info: ConditionInfoData) {
225 self.condition_info = Some(info);
226 }
227
228 pub fn is_set(&self, name: &str) -> bool {
230 let upper = name.to_uppercase();
231 self.scopes
232 .last()
233 .is_some_and(|s| s.vars.contains_key(&upper))
234 }
235
236 pub fn is_compound_set(&self, stem: &str, resolved_tail: &str) -> bool {
239 let stem_upper = format!("{}.", stem.to_uppercase());
240 let tail_upper = resolved_tail.to_uppercase();
241 let scope = self.scopes.last().expect("environment has no scopes");
242 if let Some(stem_var) = scope.stems.get(&stem_upper) {
243 stem_var.entries.contains_key(&tail_upper) || stem_var.default.is_some()
244 } else {
245 false
246 }
247 }
248
249 fn current_scope_mut(&mut self) -> &mut Scope {
250 self.scopes.last_mut().expect("environment has no scopes")
251 }
252}
253
254impl Default for Environment {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl Scope {
261 fn new() -> Self {
262 Self {
263 vars: HashMap::new(),
264 stems: HashMap::new(),
265 exposed: Vec::new(),
266 }
267 }
268}
269
270impl StemVar {
271 fn new() -> Self {
272 Self {
273 default: None,
274 entries: HashMap::new(),
275 }
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn unset_variable_returns_name() {
285 let env = Environment::new();
286 assert_eq!(env.get("foo").as_str(), "FOO");
287 }
288
289 #[test]
290 fn set_and_get() {
291 let mut env = Environment::new();
292 env.set("name", RexxValue::new("Alice"));
293 assert_eq!(env.get("name").as_str(), "Alice");
294 }
295
296 #[test]
297 fn case_insensitive() {
298 let mut env = Environment::new();
299 env.set("Name", RexxValue::new("Bob"));
300 assert_eq!(env.get("NAME").as_str(), "Bob");
301 assert_eq!(env.get("name").as_str(), "Bob");
302 }
303
304 #[test]
305 fn stem_variables() {
306 let mut env = Environment::new();
307 env.set_compound("arr", "1", RexxValue::new("first"));
308 env.set_compound("arr", "2", RexxValue::new("second"));
309 assert_eq!(env.get_compound("arr", "1").as_str(), "first");
310 assert_eq!(env.get_compound("arr", "2").as_str(), "second");
311 assert_eq!(env.get_compound("arr", "3").as_str(), "ARR.3");
313 }
314
315 #[test]
316 fn stem_default() {
317 let mut env = Environment::new();
318 env.set_stem_default("count", RexxValue::new("0"));
319 assert_eq!(env.get_compound("count", "anything").as_str(), "0");
320 env.set_compound("count", "special", RexxValue::new("99"));
321 assert_eq!(env.get_compound("count", "special").as_str(), "99");
322 assert_eq!(env.get_compound("count", "other").as_str(), "0");
323 }
324
325 #[test]
326 fn procedure_scope() {
327 let mut env = Environment::new();
328 env.set("x", RexxValue::new("outer"));
329 env.push_procedure();
330 assert_eq!(env.get("x").as_str(), "X");
332 env.set("x", RexxValue::new("inner"));
333 assert_eq!(env.get("x").as_str(), "inner");
334 env.pop_procedure();
335 assert_eq!(env.get("x").as_str(), "outer");
336 }
337
338 #[test]
339 fn procedure_expose() {
340 let mut env = Environment::new();
341 env.set("x", RexxValue::new("shared"));
342 env.set("y", RexxValue::new("hidden"));
343 env.push_procedure_expose(&["x".into()]);
344 assert_eq!(env.get("x").as_str(), "shared");
345 assert_eq!(env.get("y").as_str(), "Y"); env.pop_procedure();
347 }
348
349 #[test]
350 fn drop_variable() {
351 let mut env = Environment::new();
352 env.set("x", RexxValue::new("42"));
353 assert!(env.is_set("x"));
354 env.drop("x");
355 assert!(!env.is_set("x"));
356 assert_eq!(env.get("x").as_str(), "X");
357 }
358}