1use shape_ast::ast::VarKind;
6use shape_ast::error::{Result, ShapeError};
7use shape_value::ValueWord;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11#[derive(Debug, Clone)]
13pub struct Variable {
14 pub value: ValueWord,
16 pub kind: VarKind,
18 pub is_initialized: bool,
20 pub is_function_scoped: bool,
23 pub format_hint: Option<String>,
25 pub format_overrides: Option<HashMap<String, ValueWord>>,
27}
28
29impl Variable {
30 pub fn new(kind: VarKind, value: Option<ValueWord>) -> Self {
32 Self::with_format(kind, value, None, None)
33 }
34
35 pub fn with_format(
37 kind: VarKind,
38 value: Option<ValueWord>,
39 format_hint: Option<String>,
40 format_overrides: Option<HashMap<String, ValueWord>>,
41 ) -> Self {
42 let is_function_scoped = matches!(kind, VarKind::Var);
43 let (value, is_initialized) = match value {
44 Some(v) => (v, true),
45 None => (ValueWord::none(), false),
46 };
47
48 Self {
49 value,
50 kind,
51 is_initialized,
52 is_function_scoped,
53 format_hint,
54 format_overrides,
55 }
56 }
57
58 pub fn can_assign(&self) -> bool {
60 match self.kind {
61 VarKind::Const => !self.is_initialized, VarKind::Let | VarKind::Var => true,
63 }
64 }
65
66 pub fn assign(&mut self, value: ValueWord) -> Result<()> {
68 if !self.can_assign() {
69 return Err(ShapeError::RuntimeError {
70 message: "Cannot assign to const variable after initialization".to_string(),
71 location: None,
72 });
73 }
74
75 self.value = value;
76 self.is_initialized = true;
77 Ok(())
78 }
79
80 pub fn get_value(&self) -> Result<&ValueWord> {
82 if !self.is_initialized {
83 return Err(ShapeError::RuntimeError {
84 message: "Variable used before initialization".to_string(),
85 location: None,
86 });
87 }
88 Ok(&self.value)
89 }
90}
91
92impl super::ExecutionContext {
93 pub fn set_variable(&mut self, name: &str, value: ValueWord) -> Result<()> {
95 self.set_variable_nb(name, value)
96 }
97
98 pub fn set_variable_nb(&mut self, name: &str, value: ValueWord) -> Result<()> {
100 for scope in self.variable_scopes.iter_mut().rev() {
102 if let Some(variable) = scope.get_mut(name) {
103 return variable.assign(value);
104 }
105 }
106
107 if let Some(scope) = self.variable_scopes.last_mut() {
109 let variable = Variable::new(VarKind::Var, Some(value));
110 scope.insert(name.to_string(), variable);
111 Ok(())
112 } else {
113 Err(ShapeError::RuntimeError {
114 message: "No scope available for variable assignment".to_string(),
115 location: None,
116 })
117 }
118 }
119
120 pub fn get_variable(&self, name: &str) -> Result<Option<ValueWord>> {
122 for scope in self.variable_scopes.iter().rev() {
124 if let Some(variable) = scope.get(name) {
125 return Ok(Some(variable.get_value()?.clone()));
126 }
127 }
128 Ok(None)
129 }
130
131 pub fn get_variable_nb(&self, name: &str) -> Result<Option<ValueWord>> {
133 for scope in self.variable_scopes.iter().rev() {
134 if let Some(variable) = scope.get(name) {
135 return Ok(Some(variable.get_value()?.clone()));
136 }
137 }
138 Ok(None)
139 }
140
141 pub fn declare_variable(
143 &mut self,
144 name: &str,
145 kind: VarKind,
146 value: Option<ValueWord>,
147 ) -> Result<()> {
148 self.declare_variable_with_format(name, kind, value, None, None)
149 }
150
151 pub fn declare_variable_with_format(
158 &mut self,
159 name: &str,
160 kind: VarKind,
161 value: Option<ValueWord>,
162 format_hint: Option<String>,
163 format_overrides: Option<HashMap<String, ValueWord>>,
164 ) -> Result<()> {
165 if let Some(current_scope) = self.variable_scopes.last() {
167 if current_scope.contains_key(name) {
168 return Err(ShapeError::RuntimeError {
169 message: format!("Variable '{}' already declared in current scope", name),
170 location: None,
171 });
172 }
173 }
174
175 if matches!(kind, VarKind::Const) && value.is_none() {
177 return Err(ShapeError::RuntimeError {
178 message: format!("const variable '{}' must be initialized", name),
179 location: None,
180 });
181 }
182
183 if let Some(scope) = self.variable_scopes.last_mut() {
185 let variable = Variable::with_format(kind, value, format_hint, format_overrides);
186 scope.insert(name.to_string(), variable);
187 Ok(())
188 } else {
189 Err(ShapeError::RuntimeError {
190 message: "No scope available for variable declaration".to_string(),
191 location: None,
192 })
193 }
194 }
195
196 pub fn get_variable_format_hint(&self, name: &str) -> Option<String> {
198 for scope in self.variable_scopes.iter().rev() {
200 if let Some(variable) = scope.get(name) {
201 return variable.format_hint.clone();
202 }
203 }
204 None
205 }
206
207 pub fn get_variable_format_overrides(&self, name: &str) -> Option<HashMap<String, ValueWord>> {
212 for scope in self.variable_scopes.iter().rev() {
214 if let Some(variable) = scope.get(name) {
215 return variable.format_overrides.clone();
216 }
217 }
218 None
219 }
220
221 pub fn get_variable_format_info(
223 &self,
224 name: &str,
225 ) -> (Option<String>, Option<HashMap<String, ValueWord>>) {
226 for scope in self.variable_scopes.iter().rev() {
227 if let Some(variable) = scope.get(name) {
228 return (
229 variable.format_hint.clone(),
230 variable.format_overrides.clone(),
231 );
232 }
233 }
234 (None, None)
235 }
236
237 pub fn declare_pattern(
239 &mut self,
240 pattern: &shape_ast::ast::DestructurePattern,
241 kind: shape_ast::ast::VarKind,
242 value: ValueWord,
243 ) -> Result<()> {
244 use shape_ast::ast::DestructurePattern;
245
246 match pattern {
247 DestructurePattern::Identifier(name, _) => {
248 self.declare_variable(name, kind, Some(value))
249 }
250 DestructurePattern::Array(patterns) => {
251 if let Some(view) = value.as_any_array() {
253 let arr = view.to_generic();
254 let mut rest_index = None;
255 for (i, pattern) in patterns.iter().enumerate() {
256 if let DestructurePattern::Rest(inner) = pattern {
257 rest_index = Some(i);
258 let rest_values = if i <= arr.len() {
259 arr[i..].to_vec()
260 } else {
261 Vec::new()
262 };
263 self.declare_pattern(
264 inner,
265 kind,
266 ValueWord::from_array(Arc::new(rest_values)),
267 )?;
268 break;
269 } else {
270 let val = arr.get(i).map(|nb| nb.clone()).unwrap_or(ValueWord::none());
271 self.declare_pattern(pattern, kind, val)?;
272 }
273 }
274
275 if rest_index.is_none() && patterns.len() > arr.len() {
276 for pattern in &patterns[arr.len()..] {
277 self.declare_pattern(pattern, kind, ValueWord::none())?;
278 }
279 }
280 Ok(())
281 } else {
282 Err(ShapeError::RuntimeError {
283 message: "Cannot destructure non-array value as array".to_string(),
284 location: None,
285 })
286 }
287 }
288 DestructurePattern::Object(fields) => {
289 if let Some(obj) = crate::type_schema::typed_object_to_hashmap(&value) {
291 for field in fields {
292 if field.key == "..." {
293 if let DestructurePattern::Rest(rest_pattern) = &field.pattern {
295 if let DestructurePattern::Identifier(rest_name, _) =
296 rest_pattern.as_ref()
297 {
298 let rest_pairs: Vec<(&str, ValueWord)> = obj
300 .iter()
301 .filter(|(k, _)| {
302 !fields.iter().any(|f| f.key == **k && f.key != "...")
303 })
304 .map(|(k, v)| (k.as_str(), v.clone()))
305 .collect();
306 let rest_val =
307 crate::type_schema::typed_object_from_pairs(&rest_pairs);
308 self.declare_variable(rest_name, kind, Some(rest_val))?;
309 }
310 }
311 } else {
312 let val = obj.get(&field.key).cloned().unwrap_or(ValueWord::none());
313 self.declare_pattern(&field.pattern, kind, val)?;
314 }
315 }
316 Ok(())
317 } else {
318 Err(ShapeError::RuntimeError {
319 message: "Cannot destructure non-object value as object".to_string(),
320 location: None,
321 })
322 }
323 }
324 DestructurePattern::Rest(_) => {
325 Err(ShapeError::RuntimeError {
327 message: "Rest pattern cannot be used at top level".to_string(),
328 location: None,
329 })
330 }
331 DestructurePattern::Decomposition(bindings) => {
332 if crate::type_schema::typed_object_to_hashmap(&value).is_some() {
334 for binding in bindings {
335 self.declare_variable(&binding.name, kind, Some(value.clone()))?;
336 }
337 Ok(())
338 } else {
339 Err(ShapeError::RuntimeError {
340 message: "Cannot decompose non-object value".to_string(),
341 location: None,
342 })
343 }
344 }
345 }
346 }
347
348 pub fn set_pattern(
350 &mut self,
351 pattern: &shape_ast::ast::DestructurePattern,
352 value: ValueWord,
353 ) -> Result<()> {
354 use shape_ast::ast::DestructurePattern;
355
356 match pattern {
357 DestructurePattern::Identifier(name, _) => self.set_variable(name, value),
358 DestructurePattern::Array(patterns) => {
359 if let Some(view) = value.as_any_array() {
361 let arr = view.to_generic();
362 let mut rest_index = None;
363 for (i, pattern) in patterns.iter().enumerate() {
364 if let DestructurePattern::Rest(inner) = pattern {
365 rest_index = Some(i);
366 let rest_values = if i <= arr.len() {
367 arr[i..].to_vec()
368 } else {
369 Vec::new()
370 };
371 self.set_pattern(inner, ValueWord::from_array(Arc::new(rest_values)))?;
372 break;
373 } else {
374 let val = arr.get(i).map(|nb| nb.clone()).unwrap_or(ValueWord::none());
375 self.set_pattern(pattern, val)?;
376 }
377 }
378
379 if rest_index.is_none() && patterns.len() > arr.len() {
380 for pattern in &patterns[arr.len()..] {
381 self.set_pattern(pattern, ValueWord::none())?;
382 }
383 }
384 Ok(())
385 } else {
386 Err(ShapeError::RuntimeError {
387 message: "Cannot destructure non-array value as array".to_string(),
388 location: None,
389 })
390 }
391 }
392 DestructurePattern::Object(fields) => {
393 if let Some(obj) = crate::type_schema::typed_object_to_hashmap(&value) {
395 for field in fields {
396 if field.key == "..." {
397 if let DestructurePattern::Rest(rest_pattern) = &field.pattern {
399 if let DestructurePattern::Identifier(rest_name, _) =
400 rest_pattern.as_ref()
401 {
402 let rest_pairs: Vec<(&str, ValueWord)> = obj
404 .iter()
405 .filter(|(k, _)| {
406 !fields.iter().any(|f| f.key == **k && f.key != "...")
407 })
408 .map(|(k, v)| (k.as_str(), v.clone()))
409 .collect();
410 let rest_val =
411 crate::type_schema::typed_object_from_pairs(&rest_pairs);
412 self.set_variable(rest_name, rest_val)?;
413 }
414 }
415 } else {
416 let val = obj.get(&field.key).cloned().unwrap_or(ValueWord::none());
417 self.set_pattern(&field.pattern, val)?;
418 }
419 }
420 Ok(())
421 } else {
422 Err(ShapeError::RuntimeError {
423 message: "Cannot destructure non-object value as object".to_string(),
424 location: None,
425 })
426 }
427 }
428 DestructurePattern::Rest(_) => {
429 Err(ShapeError::RuntimeError {
431 message: "Rest pattern cannot be used at top level".to_string(),
432 location: None,
433 })
434 }
435 DestructurePattern::Decomposition(bindings) => {
436 if crate::type_schema::typed_object_to_hashmap(&value).is_some() {
438 for binding in bindings {
439 self.set_variable(&binding.name, value.clone())?;
442 }
443 Ok(())
444 } else {
445 Err(ShapeError::RuntimeError {
446 message: "Cannot decompose non-object value".to_string(),
447 location: None,
448 })
449 }
450 }
451 }
452 }
453
454 pub fn get_all_variable_names(&self) -> Vec<String> {
456 let mut names = Vec::new();
457 for scope in &self.variable_scopes {
459 for name in scope.keys() {
460 if !names.contains(name) {
461 names.push(name.clone());
462 }
463 }
464 }
465 names
466 }
467
468 pub fn get_variable_kind(&self, name: &str) -> Option<VarKind> {
470 for scope in self.variable_scopes.iter().rev() {
472 if let Some(variable) = scope.get(name) {
473 return Some(variable.kind);
474 }
475 }
476 None
477 }
478
479 pub fn root_scope_binding_names(&self) -> Vec<String> {
484 if let Some(root_scope) = self.variable_scopes.first() {
485 root_scope.keys().cloned().collect()
486 } else {
487 Vec::new()
488 }
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 #[test]
497 fn test_variable_let_creation() {
498 let var = Variable::new(VarKind::Let, Some(ValueWord::from_f64(42.0)));
499 assert!(var.is_initialized);
500 assert!(!var.is_function_scoped);
501 assert!(var.can_assign());
502 }
503
504 #[test]
505 fn test_variable_const_creation() {
506 let var = Variable::new(VarKind::Const, Some(ValueWord::from_f64(42.0)));
507 assert!(var.is_initialized);
508 assert!(!var.can_assign()); }
510
511 #[test]
512 fn test_variable_var_creation() {
513 let var = Variable::new(
514 VarKind::Var,
515 Some(ValueWord::from_string(std::sync::Arc::new(
516 "hello".to_string(),
517 ))),
518 );
519 assert!(var.is_initialized);
520 assert!(var.is_function_scoped);
521 assert!(var.can_assign());
522 }
523
524 #[test]
525 fn test_variable_uninitialized() {
526 let var = Variable::new(VarKind::Let, None);
527 assert!(!var.is_initialized);
528 assert!(var.get_value().is_err());
529 }
530
531 #[test]
532 fn test_variable_assignment() {
533 let mut var = Variable::new(VarKind::Let, Some(ValueWord::from_f64(1.0)));
534 assert!(var.assign(ValueWord::from_f64(2.0)).is_ok());
535 assert_eq!(var.get_value().unwrap().as_f64(), Some(2.0));
536 }
537
538 #[test]
539 fn test_const_reassignment_fails() {
540 let mut var = Variable::new(VarKind::Const, Some(ValueWord::from_f64(1.0)));
541 assert!(var.assign(ValueWord::from_f64(2.0)).is_err());
542 }
543
544 #[test]
545 fn test_const_initial_assignment() {
546 let mut var = Variable::new(VarKind::Const, None);
547 assert!(var.can_assign()); assert!(var.assign(ValueWord::from_f64(42.0)).is_ok());
549 assert!(!var.can_assign()); }
551
552 #[test]
557 fn test_variable_with_format_overrides() {
558 let mut overrides = HashMap::new();
559 overrides.insert("decimals".to_string(), ValueWord::from_f64(4.0));
560
561 let var = Variable::with_format(
562 VarKind::Let,
563 Some(ValueWord::from_f64(0.1234)),
564 Some("Percent".to_string()),
565 Some(overrides.clone()),
566 );
567
568 assert!(var.is_initialized);
569 assert_eq!(var.format_hint, Some("Percent".to_string()));
570 assert!(var.format_overrides.is_some());
571 let stored_overrides = var.format_overrides.unwrap();
572 assert_eq!(
573 stored_overrides.get("decimals").and_then(|v| v.as_f64()),
574 Some(4.0)
575 );
576 }
577
578 #[test]
579 fn test_context_declare_variable_with_format() {
580 use super::super::ExecutionContext;
581
582 let mut ctx = ExecutionContext::new_empty();
583 let mut overrides = HashMap::new();
584 overrides.insert("decimals".to_string(), ValueWord::from_f64(4.0));
585
586 ctx.declare_variable_with_format(
587 "rate",
588 VarKind::Let,
589 Some(ValueWord::from_f64(0.15)),
590 Some("Percent".to_string()),
591 Some(overrides),
592 )
593 .unwrap();
594
595 let hint = ctx.get_variable_format_hint("rate");
597 assert_eq!(hint, Some("Percent".to_string()));
598
599 let stored_overrides = ctx.get_variable_format_overrides("rate");
601 assert!(stored_overrides.is_some());
602 assert_eq!(
603 stored_overrides
604 .unwrap()
605 .get("decimals")
606 .and_then(|v| v.as_f64()),
607 Some(4.0)
608 );
609
610 let (hint, overrides) = ctx.get_variable_format_info("rate");
612 assert_eq!(hint, Some("Percent".to_string()));
613 assert!(overrides.is_some());
614 }
615
616 #[test]
617 fn test_context_get_format_info_not_found() {
618 use super::super::ExecutionContext;
619
620 let ctx = ExecutionContext::new_empty();
621 let (hint, overrides) = ctx.get_variable_format_info("nonexistent");
622 assert!(hint.is_none());
623 assert!(overrides.is_none());
624 }
625}