kotoba_jsonnet/eval/
context.rs

1//! Evaluation context for managing variable scopes and evaluation state
2
3use crate::error::{JsonnetError, Result};
4use crate::value::JsonnetValue;
5use std::collections::HashMap;
6
7/// A single variable scope
8#[derive(Debug, Clone)]
9pub struct Scope {
10    variables: HashMap<String, JsonnetValue>,
11}
12
13impl Scope {
14    pub fn new() -> Self {
15        Scope {
16            variables: HashMap::new(),
17        }
18    }
19
20    pub fn get(&self, name: &str) -> Option<&JsonnetValue> {
21        self.variables.get(name)
22    }
23
24    pub fn set(&mut self, name: String, value: JsonnetValue) {
25        self.variables.insert(name, value);
26    }
27
28    pub fn contains(&self, name: &str) -> bool {
29        self.variables.contains_key(name)
30    }
31
32    pub fn variables(&self) -> &HashMap<String, JsonnetValue> {
33        &self.variables
34    }
35}
36
37/// Evaluation context that manages variable scopes and evaluation state
38#[derive(Debug)]
39pub struct Context {
40    /// Global scope for top-level variables and functions
41    global_scope: Scope,
42    /// Stack of local scopes for function calls and blocks
43    local_scopes: Vec<Scope>,
44    /// Current evaluation depth (for recursion detection)
45    depth: usize,
46    /// Maximum allowed evaluation depth
47    max_depth: usize,
48}
49
50impl Context {
51    pub fn new() -> Self {
52        Context {
53            global_scope: Scope::new(),
54            local_scopes: Vec::new(),
55            depth: 0,
56            max_depth: 100, // Default recursion limit
57        }
58    }
59
60    pub fn with_max_depth(max_depth: usize) -> Self {
61        Context {
62            global_scope: Scope::new(),
63            local_scopes: Vec::new(),
64            depth: 0,
65            max_depth,
66        }
67    }
68
69    /// Get the current evaluation depth
70    pub fn depth(&self) -> usize {
71        self.depth
72    }
73
74    /// Check if we've exceeded the maximum depth
75    pub fn check_depth(&self) -> Result<()> {
76        if self.depth >= self.max_depth {
77            return Err(JsonnetError::runtime_error(
78                format!("Maximum evaluation depth ({}) exceeded", self.max_depth)
79            ));
80        }
81        Ok(())
82    }
83
84    /// Increment evaluation depth
85    pub fn push_depth(&mut self) -> Result<()> {
86        self.depth += 1;
87        self.check_depth()
88    }
89
90    /// Decrement evaluation depth
91    pub fn pop_depth(&mut self) {
92        if self.depth > 0 {
93            self.depth -= 1;
94        }
95    }
96
97    /// Get a variable by name, searching from local to global scope
98    pub fn get_variable(&self, name: &str) -> Option<&JsonnetValue> {
99        // First check local scopes (from innermost to outermost)
100        for scope in self.local_scopes.iter().rev() {
101            if let Some(value) = scope.get(name) {
102                return Some(value);
103            }
104        }
105        // Then check global scope
106        self.global_scope.get(name)
107    }
108
109    /// Set a variable in the current scope (local if available, otherwise global)
110    pub fn set_variable(&mut self, name: String, value: JsonnetValue) {
111        if let Some(scope) = self.local_scopes.last_mut() {
112            scope.set(name, value);
113        } else {
114            self.global_scope.set(name, value);
115        }
116    }
117
118    /// Set a variable in the global scope specifically
119    pub fn set_global(&mut self, name: String, value: JsonnetValue) {
120        self.global_scope.set(name, value);
121    }
122
123    /// Check if a variable exists in any scope
124    pub fn has_variable(&self, name: &str) -> bool {
125        // Check local scopes first
126        for scope in self.local_scopes.iter().rev() {
127            if scope.contains(name) {
128                return true;
129            }
130        }
131        // Then check global scope
132        self.global_scope.contains(name)
133    }
134
135    /// Push a new local scope
136    pub fn push_scope(&mut self) {
137        self.local_scopes.push(Scope::new());
138    }
139
140    /// Pop the current local scope
141    pub fn pop_scope(&mut self) -> Option<Scope> {
142        self.local_scopes.pop()
143    }
144
145    /// Get the current scope (for setting variables)
146    pub fn current_scope(&mut self) -> Option<&mut Scope> {
147        self.local_scopes.last_mut()
148    }
149
150    /// Get the global scope
151    pub fn global_scope(&self) -> &Scope {
152        &self.global_scope
153    }
154
155    /// Get the global scope mutably
156    pub fn global_scope_mut(&mut self) -> &mut Scope {
157        &mut self.global_scope
158    }
159
160    /// Get all variables from all scopes (for debugging)
161    pub fn all_variables(&self) -> HashMap<String, &JsonnetValue> {
162        let mut result = HashMap::new();
163
164        // Add global variables
165        for (name, value) in self.global_scope.variables() {
166            result.insert(name.clone(), value);
167        }
168
169        // Add local variables (later scopes override earlier ones)
170        for scope in &self.local_scopes {
171            for (name, value) in scope.variables() {
172                result.insert(name.clone(), value);
173            }
174        }
175
176        result
177    }
178
179    /// Create a new context with the same global scope but empty local scopes
180    pub fn fork(&self) -> Self {
181        Context {
182            global_scope: self.global_scope.clone(),
183            local_scopes: Vec::new(),
184            depth: 0,
185            max_depth: self.max_depth,
186        }
187    }
188
189    /// Merge local scopes back into global scope (for top-level evaluation)
190    pub fn merge_locals_to_global(&mut self) {
191        for scope in &self.local_scopes {
192            for (name, value) in scope.variables() {
193                self.global_scope.set(name.clone(), value.clone());
194            }
195        }
196        self.local_scopes.clear();
197    }
198}
199
200impl Default for Context {
201    fn default() -> Self {
202        Self::new()
203    }
204}