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
260pub struct EnvVars<'a>(&'a mut Environment);
266
267impl<'a> EnvVars<'a> {
268 pub(crate) fn new(env: &'a mut Environment) -> Self {
269 Self(env)
270 }
271
272 pub fn get(&self, name: &str) -> RexxValue {
274 self.0.get(name)
275 }
276
277 pub fn set(&mut self, name: &str, value: RexxValue) {
279 self.0.set(name, value);
280 }
281
282 pub fn get_compound(&self, stem: &str, resolved_tail: &str) -> RexxValue {
284 self.0.get_compound(stem, resolved_tail)
285 }
286
287 pub fn set_compound(&mut self, stem: &str, resolved_tail: &str, value: RexxValue) {
289 self.0.set_compound(stem, resolved_tail, value);
290 }
291
292 pub fn set_stem_default(&mut self, stem: &str, value: RexxValue) {
294 self.0.set_stem_default(stem, value);
295 }
296
297 pub fn drop(&mut self, name: &str) {
299 self.0.drop(name);
300 }
301
302 pub fn is_set(&self, name: &str) -> bool {
304 self.0.is_set(name)
305 }
306
307 pub fn is_compound_set(&self, stem: &str, resolved_tail: &str) -> bool {
309 self.0.is_compound_set(stem, resolved_tail)
310 }
311}
312
313impl Scope {
314 fn new() -> Self {
315 Self {
316 vars: HashMap::new(),
317 stems: HashMap::new(),
318 exposed: Vec::new(),
319 }
320 }
321}
322
323impl StemVar {
324 fn new() -> Self {
325 Self {
326 default: None,
327 entries: HashMap::new(),
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn unset_variable_returns_name() {
338 let env = Environment::new();
339 assert_eq!(env.get("foo").as_str(), "FOO");
340 }
341
342 #[test]
343 fn set_and_get() {
344 let mut env = Environment::new();
345 env.set("name", RexxValue::new("Alice"));
346 assert_eq!(env.get("name").as_str(), "Alice");
347 }
348
349 #[test]
350 fn case_insensitive() {
351 let mut env = Environment::new();
352 env.set("Name", RexxValue::new("Bob"));
353 assert_eq!(env.get("NAME").as_str(), "Bob");
354 assert_eq!(env.get("name").as_str(), "Bob");
355 }
356
357 #[test]
358 fn stem_variables() {
359 let mut env = Environment::new();
360 env.set_compound("arr", "1", RexxValue::new("first"));
361 env.set_compound("arr", "2", RexxValue::new("second"));
362 assert_eq!(env.get_compound("arr", "1").as_str(), "first");
363 assert_eq!(env.get_compound("arr", "2").as_str(), "second");
364 assert_eq!(env.get_compound("arr", "3").as_str(), "ARR.3");
366 }
367
368 #[test]
369 fn stem_default() {
370 let mut env = Environment::new();
371 env.set_stem_default("count", RexxValue::new("0"));
372 assert_eq!(env.get_compound("count", "anything").as_str(), "0");
373 env.set_compound("count", "special", RexxValue::new("99"));
374 assert_eq!(env.get_compound("count", "special").as_str(), "99");
375 assert_eq!(env.get_compound("count", "other").as_str(), "0");
376 }
377
378 #[test]
379 fn procedure_scope() {
380 let mut env = Environment::new();
381 env.set("x", RexxValue::new("outer"));
382 env.push_procedure();
383 assert_eq!(env.get("x").as_str(), "X");
385 env.set("x", RexxValue::new("inner"));
386 assert_eq!(env.get("x").as_str(), "inner");
387 env.pop_procedure();
388 assert_eq!(env.get("x").as_str(), "outer");
389 }
390
391 #[test]
392 fn procedure_expose() {
393 let mut env = Environment::new();
394 env.set("x", RexxValue::new("shared"));
395 env.set("y", RexxValue::new("hidden"));
396 env.push_procedure_expose(&["x".into()]);
397 assert_eq!(env.get("x").as_str(), "shared");
398 assert_eq!(env.get("y").as_str(), "Y"); env.pop_procedure();
400 }
401
402 #[test]
403 fn drop_variable() {
404 let mut env = Environment::new();
405 env.set("x", RexxValue::new("42"));
406 assert!(env.is_set("x"));
407 env.drop("x");
408 assert!(!env.is_set("x"));
409 assert_eq!(env.get("x").as_str(), "X");
410 }
411}