1mod function;
2mod scopes;
3mod variable;
4
5use std::collections::HashMap;
6
7pub(super) use function::FunctionMap;
8use phf::phf_map;
9pub(super) use scopes::SessionScope;
10use thiserror_no_std::Error;
11pub(super) use variable::{Scope, VarName};
12
13use crate::parser::{RuntimeTypeTrait, Val, value::ScriptBlock};
14#[derive(Error, Debug, PartialEq, Clone)]
15pub enum VariableError {
16 #[error("Variable \"{0}\" is not defined")]
17 NotDefined(String),
18 #[error("Cannot overwrite variable \"{0}\" because it is read-only or constant.")]
19 ReadOnly(String),
20}
21
22pub type VariableResult<T> = core::result::Result<T, VariableError>;
23pub type VariableMap = HashMap<String, Val>;
24
25#[derive(Clone, Default)]
26pub struct Variables {
27 env: VariableMap,
28 global_scope: VariableMap,
29 script_scope: VariableMap,
30 scope_sessions_stack: Vec<VariableMap>,
31 state: State,
32 force_var_eval: bool,
33 values_persist: bool,
34 global_functions: FunctionMap,
35 script_functions: FunctionMap,
36 top_scope: TopScope,
37 }
43
44#[derive(Default, Clone)]
45pub(super) enum TopScope {
46 #[default]
47 Session,
48 Script,
49}
50
51impl From<Scope> for TopScope {
52 fn from(scope: Scope) -> Self {
53 match scope {
54 Scope::Global => TopScope::Session,
55 Scope::Script => TopScope::Script,
56 _ => TopScope::Script,
57 }
58 }
59}
60
61#[derive(Clone)]
62enum State {
63 TopScope(TopScope),
64 Stack(u32),
65}
66
67impl Default for State {
68 fn default() -> Self {
69 State::TopScope(TopScope::default())
70 }
71}
72
73impl Variables {
74 const PREDEFINED_VARIABLES: phf::Map<&'static str, Val> = phf_map! {
75 "true" => Val::Bool(true),
76 "false" => Val::Bool(false),
77 "null" => Val::Null,
78 };
79
80 pub(crate) fn set_ps_item(&mut self, ps_item: Val) {
81 let _ = self.set(
82 &VarName::new_with_scope(Scope::Special, "$PSItem".into()),
83 ps_item.clone(),
84 );
85 let _ = self.set(
86 &VarName::new_with_scope(Scope::Special, "$_".into()),
87 ps_item,
88 );
89 }
90
91 pub(crate) fn reset_ps_item(&mut self) {
92 let _ = self.set(
93 &VarName::new_with_scope(Scope::Special, "$PSItem".into()),
94 Val::Null,
95 );
96 let _ = self.set(
97 &VarName::new_with_scope(Scope::Special, "$_".into()),
98 Val::Null,
99 );
100 }
101
102 pub fn set_status(&mut self, b: bool) {
103 let _ = self.set(
104 &VarName::new_with_scope(Scope::Special, "$?".into()),
105 Val::Bool(b),
106 );
107 }
108
109 pub fn status(&mut self) -> bool {
110 let Some(Val::Bool(b)) =
111 self.get_without_types(&VarName::new_with_scope(Scope::Special, "$?".into()))
112 else {
113 return false;
114 };
115 b
116 }
117
118 pub fn load_from_file(
119 &mut self,
120 path: &std::path::Path,
121 ) -> Result<(), Box<dyn std::error::Error>> {
122 let mut config_parser = configparser::ini::Ini::new();
123 let map = config_parser.load(path)?;
124 self.load(map)
125 }
126
127 pub fn load_from_string(&mut self, ini_string: &str) -> Result<(), Box<dyn std::error::Error>> {
128 let mut config_parser = configparser::ini::Ini::new();
129 let map = config_parser.read(ini_string.into())?;
130 self.load(map)
131 }
132
133 pub(super) fn init(&mut self, scope: TopScope) {
134 if !self.values_persist {
135 self.script_scope.clear();
136 }
137 self.scope_sessions_stack.clear();
138 self.state = State::TopScope(scope);
139 }
140
141 fn load(
142 &mut self,
143 conf_map: HashMap<String, HashMap<String, Option<String>>>,
144 ) -> Result<(), Box<dyn std::error::Error>> {
145 for (section_name, properties) in conf_map {
146 for (key, value) in properties {
147 let Some(value) = value else {
148 continue;
149 };
150
151 let var_name = match section_name.as_str() {
152 "global" => VarName::new_with_scope(Scope::Global, key.to_lowercase()),
153 "script" => VarName::new_with_scope(Scope::Script, key.to_lowercase()),
154 "env" => VarName::new_with_scope(Scope::Env, key.to_lowercase()),
155 _ => {
156 continue;
157 }
158 };
159
160 let parsed_value = if let Ok(bool_val) = value.parse::<bool>() {
162 Val::Bool(bool_val)
163 } else if let Ok(int_val) = value.parse::<i64>() {
164 Val::Int(int_val)
165 } else if let Ok(float_val) = value.parse::<f64>() {
166 Val::Float(float_val)
167 } else if value.is_empty() {
168 Val::Null
169 } else {
170 Val::String(value.clone().into())
171 };
172
173 if let Err(err) = self.set(&var_name, parsed_value.clone()) {
175 log::error!("Failed to set variable {:?}: {}", var_name, err);
176 }
177 }
178 }
179 Ok(())
180 }
181
182 pub(crate) fn script_scope(&self) -> VariableMap {
183 self.script_scope.clone()
184 }
185
186 pub(crate) fn get_env(&self) -> VariableMap {
187 self.env.clone()
188 }
189
190 pub(crate) fn get_global(&self) -> VariableMap {
191 self.global_scope.clone()
192 }
193
194 pub(crate) fn add_script_function(&mut self, name: String, func: ScriptBlock) {
195 self.script_functions.insert(name, func);
196 }
197
198 pub(crate) fn add_global_function(&mut self, name: String, func: ScriptBlock) {
199 self.global_functions.insert(name, func);
200 }
201
202 pub(crate) fn clear_script_functions(&mut self) {
203 self.script_functions.clear();
204 }
205
206 pub fn new() -> Variables {
229 Default::default()
230 }
231
232 pub fn force_eval() -> Self {
267 Self {
268 force_var_eval: true,
269 ..Default::default()
270 }
271 }
272
273 #[allow(dead_code)]
275 pub(crate) fn values_persist(mut self) -> Self {
276 self.values_persist = true;
277 self
278 }
279
280 pub fn env() -> Variables {
303 let mut vars = Variables::new();
304
305 for (key, value) in std::env::vars() {
307 vars.env
310 .insert(key.to_lowercase(), Val::String(value.into()));
311 }
312 vars
313 }
314
315 pub fn from_ini_string(ini_string: &str) -> Result<Self, Box<dyn std::error::Error>> {
360 let mut variables = Self::new();
361 variables.load_from_string(ini_string)?;
362 Ok(variables)
363 }
364
365 pub fn from_ini_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
367 let mut variables = Self::new();
368 variables.load_from_file(path)?;
369 Ok(variables)
370 }
371
372 fn top_scope(&self, scope: Option<&TopScope>) -> &VariableMap {
373 let scope = if let Some(s) = scope {
374 s
375 } else {
376 &self.top_scope
377 };
378 match scope {
379 TopScope::Session => &self.global_scope,
380 TopScope::Script => &self.script_scope,
381 }
382 }
383
384 fn mut_top_scope(&mut self, scope: Option<&TopScope>) -> &mut VariableMap {
385 let scope = if let Some(s) = scope {
386 s
387 } else {
388 &self.top_scope
389 };
390 match scope {
391 TopScope::Session => &mut self.global_scope,
392 TopScope::Script => &mut self.script_scope,
393 }
394 }
395
396 fn const_map_from_scope(&self, scope: &Scope) -> &VariableMap {
397 match scope {
398 Scope::Global => &self.global_scope,
399 Scope::Script => &self.script_scope,
400 Scope::Env => &self.env,
401 Scope::Local => match &self.state {
402 State::TopScope(scope) => self.top_scope(Some(scope)),
403 State::Stack(depth) => {
404 if *depth < self.scope_sessions_stack.len() as u32 {
405 &self.scope_sessions_stack[*depth as usize]
406 } else {
407 &self.script_scope
408 }
409 }
410 },
411 Scope::Special => {
412 &self.global_scope }
414 }
415 }
416
417 fn local_scope(&mut self) -> &mut VariableMap {
418 match &mut self.state {
419 State::TopScope(scope) => {
420 let scope = scope.clone();
421 self.mut_top_scope(Some(&scope))
422 }
423 State::Stack(depth) => {
424 if *depth < self.scope_sessions_stack.len() as u32 {
425 &mut self.scope_sessions_stack[*depth as usize]
426 } else {
427 &mut self.global_scope
428 }
429 }
430 }
431 }
432 fn map_from_scope(&mut self, scope: Option<&Scope>) -> &mut VariableMap {
433 match scope {
434 Some(Scope::Global) => &mut self.global_scope,
435 Some(Scope::Script) => &mut self.script_scope,
436 Some(Scope::Env) => &mut self.env,
437 Some(Scope::Local) => self.local_scope(),
438 Some(Scope::Special) => {
439 &mut self.global_scope }
441 None => self.mut_top_scope(None),
442 }
443 }
444
445 pub(crate) fn set(&mut self, var_name: &VarName, val: Val) -> VariableResult<()> {
457 let var = self.find_mut_variable_in_scopes(var_name)?;
458
459 if let Some(variable) = var {
460 *variable = val;
461 } else {
462 let map = self.map_from_scope(var_name.scope.as_ref());
463 map.insert(var_name.name.to_ascii_lowercase(), val);
464 }
465
466 Ok(())
467 }
468
469 pub(crate) fn set_local(&mut self, name: &str, val: Val) -> VariableResult<()> {
470 let var_name = VarName::new_with_scope(Scope::Local, name.to_ascii_lowercase());
471 self.set(&var_name, val)
472 }
473
474 fn find_mut_variable_in_scopes(
475 &mut self,
476 var_name: &VarName,
477 ) -> VariableResult<Option<&mut Val>> {
478 let name = var_name.name.to_ascii_lowercase();
479 let name_str = name.as_str();
480
481 if let Some(scope) = &var_name.scope
482 && self.const_map_from_scope(scope).contains_key(name_str)
483 {
484 Ok(self.map_from_scope(Some(scope)).get_mut(name_str))
485 } else {
486 if Self::PREDEFINED_VARIABLES.contains_key(name_str) {
487 return Err(VariableError::ReadOnly(name.clone()));
488 }
489
490 for local_scope in self.scope_sessions_stack.iter_mut().rev() {
492 if local_scope.contains_key(name_str) {
493 return Ok(local_scope.get_mut(name_str));
494 }
495 }
496
497 if self.script_scope.contains_key(name_str) {
498 return Ok(self.script_scope.get_mut(name_str));
499 }
500
501 if self.global_scope.contains_key(name_str) {
502 return Ok(self.global_scope.get_mut(name_str));
503 }
504
505 Ok(None)
506 }
507 }
508
509 pub(crate) fn get(
520 &self,
521 var_name: &VarName,
522 types_map: &HashMap<String, Box<dyn RuntimeTypeTrait>>,
523 ) -> Option<Val> {
524 let var = self.find_variable_in_scopes(var_name);
525
526 if self.force_var_eval && var.is_none() {
527 if let Some(rt) = types_map.get(var_name.name.as_str()) {
528 Some(Val::RuntimeType(rt.clone_rt()))
529 } else {
530 Some(Val::Null)
531 }
532 } else {
533 var.cloned()
534 }
535 }
536
537 pub(crate) fn get_without_types(&self, var_name: &VarName) -> Option<Val> {
538 let var = self.find_variable_in_scopes(var_name);
539
540 if self.force_var_eval && var.is_none() {
541 Some(Val::Null)
542 } else {
543 var.cloned()
544 }
545 }
546
547 fn find_variable_in_scopes(&self, var_name: &VarName) -> Option<&Val> {
548 let name = var_name.name.to_ascii_lowercase();
549 let name_str = name.as_str();
550
551 if let Some(scope) = &var_name.scope {
552 let map = self.const_map_from_scope(scope);
553 let x = map.get(name_str);
554 if x.is_some() {
555 return x;
556 }
557 }
558 if Self::PREDEFINED_VARIABLES.contains_key(name_str) {
559 return Self::PREDEFINED_VARIABLES.get(name_str);
560 }
561
562 for local_scope in self.scope_sessions_stack.iter().rev() {
564 if local_scope.contains_key(name_str) {
565 return local_scope.get(name_str);
566 }
567 }
568
569 if self.script_scope.contains_key(name_str) {
570 return self.script_scope.get(name_str);
571 }
572
573 if self.global_scope.contains_key(name_str) {
574 return self.global_scope.get(name_str);
575 }
576
577 None
578 }
579
580 pub(crate) fn push_scope_session(&mut self) {
581 let current_map = self.local_scope();
582 let new_map = current_map.clone();
583
584 self.scope_sessions_stack.push(new_map);
585 self.state = State::Stack(self.scope_sessions_stack.len() as u32 - 1);
586 }
587
588 pub(crate) fn pop_scope_session(&mut self) {
589 match self.scope_sessions_stack.len() {
590 0 => {} 1 => {
592 self.scope_sessions_stack.pop();
593 self.state = State::TopScope(self.top_scope.clone());
594 }
595 _ => {
596 self.scope_sessions_stack.pop();
597 self.state = State::Stack(self.scope_sessions_stack.len() as u32 - 1);
598 }
599 }
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::Variables;
606 use crate::{PowerShellSession, PsValue};
607
608 #[test]
609 fn test_builtin_variables() {
610 let mut p = PowerShellSession::new();
611 assert_eq!(p.safe_eval(r#" $true "#).unwrap().as_str(), "True");
612 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
613 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
614 }
615
616 #[test]
617 fn test_env_variables() {
618 let v = Variables::env();
619 let mut p = PowerShellSession::new().with_variables(v);
620 assert_eq!(
621 p.safe_eval(r#" $env:path "#).unwrap().as_str(),
622 std::env::var("PATH").unwrap()
623 );
624 assert_eq!(
625 p.safe_eval(r#" $env:username "#).unwrap().as_str(),
626 std::env::var("USERNAME").unwrap()
627 );
628 assert_eq!(
629 p.safe_eval(r#" $env:tEMp "#).unwrap().as_str(),
630 std::env::var("TEMP").unwrap()
631 );
632 assert_eq!(
633 p.safe_eval(r#" $env:tMp "#).unwrap().as_str(),
634 std::env::var("TMP").unwrap()
635 );
636 assert_eq!(
637 p.safe_eval(r#" $env:cOmputername "#).unwrap().as_str(),
638 std::env::var("COMPUTERNAME").unwrap()
639 );
640 assert_eq!(
641 p.safe_eval(r#" $env:programfiles "#).unwrap().as_str(),
642 std::env::var("PROGRAMFILES").unwrap()
643 );
644 assert_eq!(
645 p.safe_eval(r#" $env:temp "#).unwrap().as_str(),
646 std::env::var("TEMP").unwrap()
647 );
648 assert_eq!(
649 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
650 .unwrap()
651 .as_str(),
652 std::env::var("ProgramFiles(x86)").unwrap()
653 );
654 let env_variables = p.env_variables();
655 assert_eq!(
656 env_variables.get("path").unwrap().to_string(),
657 std::env::var("PATH").unwrap()
658 );
659 assert_eq!(
660 env_variables.get("tmp").unwrap().to_string(),
661 std::env::var("TMP").unwrap()
662 );
663 assert_eq!(
664 env_variables.get("temp").unwrap().to_string(),
665 std::env::var("TMP").unwrap()
666 );
667 assert_eq!(
668 env_variables.get("appdata").unwrap().to_string(),
669 std::env::var("APPDATA").unwrap()
670 );
671 assert_eq!(
672 env_variables.get("username").unwrap().to_string(),
673 std::env::var("USERNAME").unwrap()
674 );
675 assert_eq!(
676 env_variables.get("programfiles").unwrap().to_string(),
677 std::env::var("PROGRAMFILES").unwrap()
678 );
679 assert_eq!(
680 env_variables.get("programfiles(x86)").unwrap().to_string(),
681 std::env::var("PROGRAMFILES(x86)").unwrap()
682 );
683 }
684
685 #[test]
686 fn test_global_variables() {
687 let v = Variables::env();
688 let mut p = PowerShellSession::new().with_variables(v);
689
690 p.parse_script(r#" $global:var_int = 5 "#).unwrap();
691 p.parse_script(r#" $global:var_string = "global";$script:var_string = "script";$local:var_string = "local" "#).unwrap();
692
693 assert_eq!(
694 p.parse_script(r#" $var_int "#).unwrap().result(),
695 PsValue::Int(5)
696 );
697 assert_eq!(
698 p.parse_script(r#" $var_string "#).unwrap().result(),
699 PsValue::String("local".into())
700 );
701
702 let global_variables = p.session_variables();
703 assert_eq!(global_variables.get("var_int").unwrap(), &PsValue::Int(5));
704 assert_eq!(
705 global_variables.get("var_string").unwrap(),
706 &PsValue::String("local".into())
707 );
708 }
709
710 #[test]
711 fn test_script_variables() {
712 let v = Variables::env();
713 let mut p = PowerShellSession::new().with_variables(v);
714
715 let script_res = p
716 .parse_script(r#" $script:var_int = 5;$var_string = "assdfa" "#)
717 .unwrap();
718 let script_variables = script_res.script_variables();
719 assert_eq!(script_variables.get("var_int"), Some(&PsValue::Int(5)));
720 assert_eq!(
721 script_variables.get("var_string"),
722 Some(&PsValue::String("assdfa".into()))
723 );
724 }
725
726 #[test]
727 fn test_env_special_cases() {
728 let v = Variables::env();
729 let mut p = PowerShellSession::new().with_variables(v);
730 p.safe_eval(r#" $global:program = $env:programfiles + "\program" "#)
731 .unwrap();
732 assert_eq!(
733 p.safe_eval(r#" $global:program "#).unwrap().as_str(),
734 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
735 );
736 assert_eq!(
737 p.safe_eval(r#" $program "#).unwrap().as_str(),
738 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
739 );
740
741 assert_eq!(
742 p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} = 5;${Env:ProgramFiles(x86):adsf} "#)
743 .unwrap()
744 .as_str(),
745 5.to_string()
746 );
747 assert_eq!(
748 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
749 .unwrap()
750 .as_str(),
751 std::env::var("ProgramFiles(x86)").unwrap()
752 );
753 }
754
755 #[test]
756 fn special_last_error() {
757 let input = r#"3+"01234 ?";$a=5;$a;$?"#;
758
759 let mut p = PowerShellSession::new();
760 assert_eq!(p.safe_eval(input).unwrap().as_str(), "True");
761
762 let input = r#"3+"01234 ?";$?"#;
763 assert_eq!(p.safe_eval(input).unwrap().as_str(), "False");
764 }
765
766 #[test]
767 fn test_from_ini() {
768 let input = r#"[global]
769name = radek
770age = 30
771is_admin = true
772height = 5.9
773empty_value =
774
775[script]
776local_var = "local_value"
777 "#;
778 let mut variables = Variables::new().values_persist();
779 variables.load_from_string(input).unwrap();
780 let mut p = PowerShellSession::new().with_variables(variables);
781
782 assert_eq!(
783 p.parse_script(r#" $global:name "#).unwrap().result(),
784 PsValue::String("radek".into())
785 );
786 assert_eq!(
787 p.parse_script(r#" $global:age "#).unwrap().result(),
788 PsValue::Int(30)
789 );
790 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
791 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
792 assert_eq!(
793 p.safe_eval(r#" $script:local_var "#).unwrap().as_str(),
794 "\"local_value\""
795 );
796 assert_eq!(
797 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
798 "\"local_value\""
799 );
800 }
801
802 #[test]
803 fn test_from_ini_string() {
804 let input = r#"[global]
805name = radek
806age = 30
807is_admin = true
808height = 5.9
809empty_value =
810
811[script]
812local_var = "local_value"
813 "#;
814
815 let variables = Variables::from_ini_string(input).unwrap().values_persist();
816 let mut p = PowerShellSession::new().with_variables(variables);
817 assert_eq!(
818 p.parse_script(r#" $global:name "#).unwrap().result(),
819 PsValue::String("radek".into())
820 );
821 assert_eq!(
822 p.parse_script(r#" $global:age "#).unwrap().result(),
823 PsValue::Int(30)
824 );
825 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
826 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
827 assert_eq!(
828 p.safe_eval(r#" $script:local_var "#).unwrap().as_str(),
829 "\"local_value\""
830 );
831 assert_eq!(
832 p.safe_eval(r#" $local_var "#).unwrap().as_str(),
833 "\"local_value\""
834 );
835
836 assert_eq!(
837 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
838 "\"local_value\""
839 );
840 }
841}