1use shape_ast::ast::VarKind;
6use shape_ast::error::{Result, ShapeError};
7use shape_value::KindedSlot;
15use std::collections::HashMap;
16
17#[derive(Debug, Clone)]
19pub struct Variable {
20 pub value: KindedSlot,
24 pub kind: VarKind,
26 pub is_initialized: bool,
28 pub is_function_scoped: bool,
31 pub format_hint: Option<String>,
33 pub format_overrides: Option<HashMap<String, KindedSlot>>,
38}
39
40impl Variable {
41 pub fn new(kind: VarKind, value: Option<KindedSlot>) -> Self {
43 Self::with_format(kind, value, None, None)
44 }
45
46 pub fn with_format(
48 kind: VarKind,
49 value: Option<KindedSlot>,
50 format_hint: Option<String>,
51 format_overrides: Option<HashMap<String, KindedSlot>>,
52 ) -> Self {
53 let is_function_scoped = matches!(kind, VarKind::Var);
54 let (value, is_initialized) = match value {
55 Some(v) => (v, true),
56 None => (KindedSlot::none(), false),
57 };
58
59 Self {
60 value,
61 kind,
62 is_initialized,
63 is_function_scoped,
64 format_hint,
65 format_overrides,
66 }
67 }
68
69 pub fn can_assign(&self) -> bool {
71 match self.kind {
72 VarKind::Const => !self.is_initialized, VarKind::Let | VarKind::Var => true,
74 }
75 }
76
77 pub fn assign(&mut self, value: KindedSlot) -> Result<()> {
79 if !self.can_assign() {
80 return Err(ShapeError::RuntimeError {
81 message: "Cannot assign to const variable after initialization".to_string(),
82 location: None,
83 });
84 }
85
86 self.value = value;
87 self.is_initialized = true;
88 Ok(())
89 }
90
91 pub fn get_value(&self) -> Result<&KindedSlot> {
93 if !self.is_initialized {
94 return Err(ShapeError::RuntimeError {
95 message: "Variable used before initialization".to_string(),
96 location: None,
97 });
98 }
99 Ok(&self.value)
100 }
101}
102
103impl super::ExecutionContext {
104 pub fn set_variable(&mut self, name: &str, value: KindedSlot) -> Result<()> {
106 for scope in self.variable_scopes.iter_mut().rev() {
108 if let Some(variable) = scope.get_mut(name) {
109 return variable.assign(value);
110 }
111 }
112
113 if let Some(scope) = self.variable_scopes.last_mut() {
115 let variable = Variable::new(VarKind::Var, Some(value));
116 scope.insert(name.to_string(), variable);
117 Ok(())
118 } else {
119 Err(ShapeError::RuntimeError {
120 message: "No scope available for variable assignment".to_string(),
121 location: None,
122 })
123 }
124 }
125
126 pub fn get_variable(&self, name: &str) -> Result<Option<KindedSlot>> {
128 for scope in self.variable_scopes.iter().rev() {
130 if let Some(variable) = scope.get(name) {
131 return Ok(Some(variable.get_value()?.clone()));
132 }
133 }
134 Ok(None)
135 }
136
137 pub fn declare_variable(
139 &mut self,
140 name: &str,
141 kind: VarKind,
142 value: Option<KindedSlot>,
143 ) -> Result<()> {
144 self.declare_variable_with_format(name, kind, value, None, None)
145 }
146
147 pub fn declare_variable_with_format(
154 &mut self,
155 name: &str,
156 kind: VarKind,
157 value: Option<KindedSlot>,
158 format_hint: Option<String>,
159 format_overrides: Option<HashMap<String, KindedSlot>>,
160 ) -> Result<()> {
161 if let Some(current_scope) = self.variable_scopes.last() {
163 if current_scope.contains_key(name) {
164 return Err(ShapeError::RuntimeError {
165 message: format!("Variable '{}' already declared in current scope", name),
166 location: None,
167 });
168 }
169 }
170
171 if matches!(kind, VarKind::Const) && value.is_none() {
173 return Err(ShapeError::RuntimeError {
174 message: format!("const variable '{}' must be initialized", name),
175 location: None,
176 });
177 }
178
179 if let Some(scope) = self.variable_scopes.last_mut() {
181 let variable = Variable::with_format(kind, value, format_hint, format_overrides);
182 scope.insert(name.to_string(), variable);
183 Ok(())
184 } else {
185 Err(ShapeError::RuntimeError {
186 message: "No scope available for variable declaration".to_string(),
187 location: None,
188 })
189 }
190 }
191
192 pub fn get_variable_format_hint(&self, name: &str) -> Option<String> {
194 for scope in self.variable_scopes.iter().rev() {
196 if let Some(variable) = scope.get(name) {
197 return variable.format_hint.clone();
198 }
199 }
200 None
201 }
202
203 pub fn get_variable_format_overrides(
212 &self,
213 name: &str,
214 ) -> Option<HashMap<String, KindedSlot>> {
215 for scope in self.variable_scopes.iter().rev() {
217 if let Some(variable) = scope.get(name) {
218 return variable.format_overrides.clone();
219 }
220 }
221 None
222 }
223
224 pub fn get_variable_format_info(
226 &self,
227 name: &str,
228 ) -> (Option<String>, Option<HashMap<String, KindedSlot>>) {
229 for scope in self.variable_scopes.iter().rev() {
230 if let Some(variable) = scope.get(name) {
231 return (
232 variable.format_hint.clone(),
233 variable.format_overrides.clone(),
234 );
235 }
236 }
237 (None, None)
238 }
239
240 pub fn get_all_variable_names(&self) -> Vec<String> {
250 let mut names = Vec::new();
251 for scope in &self.variable_scopes {
253 for name in scope.keys() {
254 if !names.contains(name) {
255 names.push(name.clone());
256 }
257 }
258 }
259 names
260 }
261
262 pub fn get_variable_kind(&self, name: &str) -> Option<VarKind> {
264 for scope in self.variable_scopes.iter().rev() {
266 if let Some(variable) = scope.get(name) {
267 return Some(variable.kind);
268 }
269 }
270 None
271 }
272
273 pub fn root_scope_binding_names(&self) -> Vec<String> {
278 if let Some(root_scope) = self.variable_scopes.first() {
279 root_scope.keys().cloned().collect()
280 } else {
281 Vec::new()
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_variable_let_creation() {
292 let var = Variable::new(VarKind::Let, Some(KindedSlot::from_number(42.0)));
293 assert!(var.is_initialized);
294 assert!(!var.is_function_scoped);
295 assert!(var.can_assign());
296 }
297
298 #[test]
299 fn test_variable_const_creation() {
300 let var = Variable::new(VarKind::Const, Some(KindedSlot::from_number(42.0)));
301 assert!(var.is_initialized);
302 assert!(!var.can_assign()); }
304
305 #[test]
306 fn test_variable_var_creation() {
307 let var = Variable::new(
308 VarKind::Var,
309 Some(KindedSlot::from_string_arc(std::sync::Arc::new(
310 "hello".to_string(),
311 ))),
312 );
313 assert!(var.is_initialized);
314 assert!(var.is_function_scoped);
315 assert!(var.can_assign());
316 }
317
318 #[test]
319 fn test_variable_uninitialized() {
320 let var = Variable::new(VarKind::Let, None);
321 assert!(!var.is_initialized);
322 assert!(var.get_value().is_err());
323 }
324
325 #[test]
326 fn test_variable_assignment() {
327 let mut var = Variable::new(VarKind::Let, Some(KindedSlot::from_number(1.0)));
328 assert!(var.assign(KindedSlot::from_number(2.0)).is_ok());
329 assert_eq!(var.get_value().unwrap().slot().as_f64(), 2.0);
330 }
331
332 #[test]
333 fn test_const_reassignment_fails() {
334 let mut var = Variable::new(VarKind::Const, Some(KindedSlot::from_number(1.0)));
335 assert!(var.assign(KindedSlot::from_number(2.0)).is_err());
336 }
337
338 #[test]
339 fn test_const_initial_assignment() {
340 let mut var = Variable::new(VarKind::Const, None);
341 assert!(var.can_assign()); assert!(var.assign(KindedSlot::from_number(42.0)).is_ok());
343 assert!(!var.can_assign()); }
345
346 #[test]
351 fn test_variable_with_format_overrides() {
352 let mut overrides = HashMap::new();
353 overrides.insert("decimals".to_string(), KindedSlot::from_number(4.0));
354
355 let var = Variable::with_format(
356 VarKind::Let,
357 Some(KindedSlot::from_number(0.1234)),
358 Some("Percent".to_string()),
359 Some(overrides.clone()),
360 );
361
362 assert!(var.is_initialized);
363 assert_eq!(var.format_hint, Some("Percent".to_string()));
364 assert!(var.format_overrides.is_some());
365 let stored_overrides = var.format_overrides.unwrap();
366 assert_eq!(
367 stored_overrides.get("decimals").map(|ks| ks.slot().as_f64()),
368 Some(4.0)
369 );
370 }
371
372 #[test]
373 fn test_context_declare_variable_with_format() {
374 use super::super::ExecutionContext;
375
376 let mut ctx = ExecutionContext::new_empty();
377 let mut overrides = HashMap::new();
378 overrides.insert("decimals".to_string(), KindedSlot::from_number(4.0));
379
380 ctx.declare_variable_with_format(
381 "rate",
382 VarKind::Let,
383 Some(KindedSlot::from_number(0.15)),
384 Some("Percent".to_string()),
385 Some(overrides),
386 )
387 .unwrap();
388
389 let hint = ctx.get_variable_format_hint("rate");
391 assert_eq!(hint, Some("Percent".to_string()));
392
393 let stored_overrides = ctx.get_variable_format_overrides("rate");
395 assert!(stored_overrides.is_some());
396 assert_eq!(
397 stored_overrides
398 .unwrap()
399 .get("decimals")
400 .map(|ks| ks.slot().as_f64()),
401 Some(4.0)
402 );
403
404 let (hint, overrides) = ctx.get_variable_format_info("rate");
406 assert_eq!(hint, Some("Percent".to_string()));
407 assert!(overrides.is_some());
408 }
409
410 #[test]
411 fn test_context_get_format_info_not_found() {
412 use super::super::ExecutionContext;
413
414 let ctx = ExecutionContext::new_empty();
415 let (hint, overrides) = ctx.get_variable_format_info("nonexistent");
416 assert!(hint.is_none());
417 assert!(overrides.is_none());
418 }
419}