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 local_scopes_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(Debug, 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.local_scopes_stack.clear();
138 self.state = State::TopScope(scope.clone());
139 self.top_scope = scope;
140 }
141
142 fn load(
143 &mut self,
144 conf_map: HashMap<String, HashMap<String, Option<String>>>,
145 ) -> Result<(), Box<dyn std::error::Error>> {
146 for (section_name, properties) in conf_map {
147 for (key, value) in properties {
148 let Some(value) = value else {
149 continue;
150 };
151
152 let var_name = match section_name.as_str() {
153 "global" => VarName::new_with_scope(Scope::Global, key.to_lowercase()),
154 "script" => VarName::new_with_scope(Scope::Script, key.to_lowercase()),
155 "env" => VarName::new_with_scope(Scope::Env, key.to_lowercase()),
156 _ => {
157 continue;
158 }
159 };
160
161 let parsed_value = if let Ok(bool_val) = value.parse::<bool>() {
163 Val::Bool(bool_val)
164 } else if let Ok(int_val) = value.parse::<i64>() {
165 Val::Int(int_val)
166 } else if let Ok(float_val) = value.parse::<f64>() {
167 Val::Float(float_val)
168 } else if value.is_empty() {
169 Val::Null
170 } else {
171 Val::String(value.clone().into())
172 };
173
174 if let Err(err) = self.set(&var_name, parsed_value.clone()) {
176 log::error!("Failed to set variable {:?}: {}", var_name, err);
177 }
178 }
179 }
180 Ok(())
181 }
182
183 pub(crate) fn script_scope(&self) -> VariableMap {
184 self.script_scope.clone()
185 }
186
187 pub(crate) fn get_env(&self) -> VariableMap {
188 self.env.clone()
189 }
190
191 pub(crate) fn get_global(&self) -> VariableMap {
192 self.global_scope.clone()
193 }
194
195 pub(crate) fn add_script_function(&mut self, name: String, func: ScriptBlock) {
196 self.script_functions.insert(name, func);
197 }
198
199 pub(crate) fn add_global_function(&mut self, name: String, func: ScriptBlock) {
200 self.global_functions.insert(name, func);
201 }
202
203 pub(crate) fn clear_script_functions(&mut self) {
204 self.script_functions.clear();
205 }
206
207 pub fn new() -> Variables {
230 Default::default()
231 }
232
233 pub fn force_eval() -> Self {
268 Self {
269 force_var_eval: true,
270 ..Default::default()
271 }
272 }
273
274 #[allow(dead_code)]
276 pub(crate) fn values_persist(mut self) -> Self {
277 self.values_persist = true;
278 self
279 }
280
281 pub fn env() -> Variables {
304 let mut vars = Variables::new();
305
306 for (key, value) in std::env::vars() {
308 vars.env
311 .insert(key.to_lowercase(), Val::String(value.into()));
312 }
313 vars
314 }
315
316 pub fn from_ini_string(ini_string: &str) -> Result<Self, Box<dyn std::error::Error>> {
361 let mut variables = Self::new();
362 variables.load_from_string(ini_string)?;
363 Ok(variables)
364 }
365
366 pub fn from_ini_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
368 let mut variables = Self::new();
369 variables.load_from_file(path)?;
370 Ok(variables)
371 }
372
373 fn top_scope(&self, scope: Option<&TopScope>) -> &VariableMap {
374 let scope = if let Some(s) = scope {
375 s
376 } else {
377 &self.top_scope
378 };
379 match scope {
380 TopScope::Session => &self.global_scope,
381 TopScope::Script => &self.script_scope,
382 }
383 }
384
385 fn mut_top_scope(&mut self, scope: Option<&TopScope>) -> &mut VariableMap {
386 let scope = if let Some(s) = scope {
387 s
388 } else {
389 &self.top_scope
390 };
391
392 match scope {
393 TopScope::Session => &mut self.global_scope,
394 TopScope::Script => &mut self.script_scope,
395 }
396 }
397
398 fn const_map_from_scope(&self, scope: &Scope) -> &VariableMap {
399 match scope {
400 Scope::Global => &self.global_scope,
401 Scope::Script => &self.script_scope,
402 Scope::Env => &self.env,
403 Scope::Local => match &self.state {
404 State::TopScope(scope) => self.top_scope(Some(scope)),
405 State::Stack(depth) => {
406 if *depth < self.local_scopes_stack.len() as u32 {
407 &self.local_scopes_stack[*depth as usize]
408 } else {
409 &self.script_scope
410 }
411 }
412 },
413 Scope::Special => {
414 &self.global_scope }
416 }
417 }
418
419 fn local_scope(&mut self) -> &mut VariableMap {
420 match &mut self.state {
421 State::TopScope(scope) => {
422 let scope = scope.clone();
423 self.mut_top_scope(Some(&scope))
424 }
425 State::Stack(depth) => {
426 if *depth < self.local_scopes_stack.len() as u32 {
427 &mut self.local_scopes_stack[*depth as usize]
428 } else {
429 &mut self.global_scope
430 }
431 }
432 }
433 }
434 fn map_from_scope(&mut self, scope: Option<&Scope>) -> &mut VariableMap {
435 match scope {
436 Some(Scope::Global) => &mut self.global_scope,
437 Some(Scope::Script) => &mut self.script_scope,
438 Some(Scope::Env) => &mut self.env,
439 Some(Scope::Local) => self.local_scope(),
440 Some(Scope::Special) => {
441 &mut self.global_scope }
443 None => self.mut_top_scope(None),
444 }
445 }
446
447 pub(crate) fn set(&mut self, var_name: &VarName, val: Val) -> VariableResult<()> {
459 let var = self.find_mut_variable_in_scopes(var_name)?;
460
461 if let Some(variable) = var {
462 *variable = val;
463 } else {
464 let map = self.map_from_scope(var_name.scope.as_ref());
465 map.insert(var_name.name.to_ascii_lowercase(), val);
466 }
467
468 Ok(())
469 }
470
471 pub(crate) fn set_local(&mut self, name: &str, val: Val) -> VariableResult<()> {
472 let var_name = VarName::new_with_scope(Scope::Local, name.to_ascii_lowercase());
473 self.set(&var_name, val)
474 }
475
476 fn find_mut_variable_in_scopes(
477 &mut self,
478 var_name: &VarName,
479 ) -> VariableResult<Option<&mut Val>> {
480 let name = var_name.name.to_ascii_lowercase();
481 let name_str = name.as_str();
482
483 if let Some(scope) = &var_name.scope
484 && self.const_map_from_scope(scope).contains_key(name_str)
485 {
486 Ok(self.map_from_scope(Some(scope)).get_mut(name_str))
487 } else {
488 if Self::PREDEFINED_VARIABLES.contains_key(name_str) {
489 return Err(VariableError::ReadOnly(name.clone()));
490 }
491
492 for local_scope in self.local_scopes_stack.iter_mut().rev() {
494 if local_scope.contains_key(name_str) {
495 return Ok(local_scope.get_mut(name_str));
496 }
497 }
498
499 if self.script_scope.contains_key(name_str) {
500 return Ok(self.script_scope.get_mut(name_str));
501 }
502
503 if self.global_scope.contains_key(name_str) {
504 return Ok(self.global_scope.get_mut(name_str));
505 }
506
507 Ok(None)
508 }
509 }
510
511 pub(crate) fn get(
522 &self,
523 var_name: &VarName,
524 types_map: &HashMap<String, Box<dyn RuntimeTypeTrait>>,
525 ) -> Option<Val> {
526 let var = self.find_variable_in_scopes(var_name);
527
528 if self.force_var_eval && var.is_none() {
529 if let Some(rt) = types_map.get(var_name.name.as_str()) {
530 Some(Val::RuntimeType(rt.clone_rt()))
531 } else {
532 Some(Val::Null)
533 }
534 } else {
535 var.cloned()
536 }
537 }
538
539 pub(crate) fn get_without_types(&self, var_name: &VarName) -> Option<Val> {
540 let var = self.find_variable_in_scopes(var_name);
541
542 if self.force_var_eval && var.is_none() {
543 Some(Val::Null)
544 } else {
545 var.cloned()
546 }
547 }
548
549 fn find_variable_in_scopes(&self, var_name: &VarName) -> Option<&Val> {
550 let name = var_name.name.to_ascii_lowercase();
551 let name_str = name.as_str();
552
553 if let Some(scope) = &var_name.scope {
554 let map = self.const_map_from_scope(scope);
555 let x = map.get(name_str);
556 if x.is_some() {
557 return x;
558 }
559 }
560 if Self::PREDEFINED_VARIABLES.contains_key(name_str) {
561 return Self::PREDEFINED_VARIABLES.get(name_str);
562 }
563
564 for local_scope in self.local_scopes_stack.iter().rev() {
566 if local_scope.contains_key(name_str) {
567 return local_scope.get(name_str);
568 }
569 }
570
571 if self.script_scope.contains_key(name_str) {
572 return self.script_scope.get(name_str);
573 }
574
575 if self.global_scope.contains_key(name_str) {
576 return self.global_scope.get(name_str);
577 }
578
579 None
580 }
581
582 pub(crate) fn push_scope_session(&mut self) {
583 let current_map = self.local_scope();
584 let new_map = current_map.clone();
585
586 self.local_scopes_stack.push(new_map);
587 self.state = State::Stack(self.local_scopes_stack.len() as u32 - 1);
588 }
589
590 pub(crate) fn pop_scope_session(&mut self) {
591 match self.local_scopes_stack.len() {
592 0 => {} 1 => {
594 self.local_scopes_stack.pop();
595 self.state = State::TopScope(self.top_scope.clone());
596 }
597 _ => {
598 self.local_scopes_stack.pop();
599 self.state = State::Stack(self.local_scopes_stack.len() as u32 - 1);
600 }
601 }
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::Variables;
608 use crate::{PowerShellSession, PsValue};
609
610 #[test]
611 fn test_builtin_variables() {
612 let mut p = PowerShellSession::new();
613 assert_eq!(p.safe_eval(r#" $true "#).unwrap().as_str(), "True");
614 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
615 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
616 }
617
618 #[test]
619 fn test_env_variables() {
620 let v = Variables::env();
621 let mut p = PowerShellSession::new().with_variables(v);
622 assert_eq!(
623 p.safe_eval(r#" $env:path "#).unwrap().as_str(),
624 std::env::var("PATH").unwrap()
625 );
626 assert_eq!(
627 p.safe_eval(r#" $env:username "#).unwrap().as_str(),
628 std::env::var("USERNAME").unwrap()
629 );
630 assert_eq!(
631 p.safe_eval(r#" $env:tEMp "#).unwrap().as_str(),
632 std::env::var("TEMP").unwrap()
633 );
634 assert_eq!(
635 p.safe_eval(r#" $env:tMp "#).unwrap().as_str(),
636 std::env::var("TMP").unwrap()
637 );
638 assert_eq!(
639 p.safe_eval(r#" $env:cOmputername "#).unwrap().as_str(),
640 std::env::var("COMPUTERNAME").unwrap()
641 );
642 assert_eq!(
643 p.safe_eval(r#" $env:programfiles "#).unwrap().as_str(),
644 std::env::var("PROGRAMFILES").unwrap()
645 );
646 assert_eq!(
647 p.safe_eval(r#" $env:temp "#).unwrap().as_str(),
648 std::env::var("TEMP").unwrap()
649 );
650 assert_eq!(
651 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
652 .unwrap()
653 .as_str(),
654 std::env::var("ProgramFiles(x86)").unwrap()
655 );
656 let env_variables = p.env_variables();
657 assert_eq!(
658 env_variables.get("path").unwrap().to_string(),
659 std::env::var("PATH").unwrap()
660 );
661 assert_eq!(
662 env_variables.get("tmp").unwrap().to_string(),
663 std::env::var("TMP").unwrap()
664 );
665 assert_eq!(
666 env_variables.get("temp").unwrap().to_string(),
667 std::env::var("TMP").unwrap()
668 );
669 assert_eq!(
670 env_variables.get("appdata").unwrap().to_string(),
671 std::env::var("APPDATA").unwrap()
672 );
673 assert_eq!(
674 env_variables.get("username").unwrap().to_string(),
675 std::env::var("USERNAME").unwrap()
676 );
677 assert_eq!(
678 env_variables.get("programfiles").unwrap().to_string(),
679 std::env::var("PROGRAMFILES").unwrap()
680 );
681 assert_eq!(
682 env_variables.get("programfiles(x86)").unwrap().to_string(),
683 std::env::var("PROGRAMFILES(x86)").unwrap()
684 );
685 }
686
687 #[test]
688 fn test_global_variables() {
689 let v = Variables::env();
690 let mut p = PowerShellSession::new().with_variables(v);
691
692 p.parse_script(r#" $global:var_int = 5 "#).unwrap();
693 p.parse_script(r#" $global:var_string = "global";$script:var_string = "script";$local:var_string = "local" "#).unwrap();
694
695 assert_eq!(
696 p.parse_script(r#" $var_int "#).unwrap().result(),
697 PsValue::Int(5)
698 );
699 assert_eq!(
700 p.parse_script(r#" $var_string "#).unwrap().result(),
701 PsValue::String("local".into())
702 );
703
704 let global_variables = p.session_variables();
705 assert_eq!(global_variables.get("var_int").unwrap(), &PsValue::Int(5));
706 assert_eq!(
707 global_variables.get("var_string").unwrap(),
708 &PsValue::String("local".into())
709 );
710 }
711
712 #[test]
713 fn test_script_variables() {
714 let v = Variables::env();
715 let mut p = PowerShellSession::new().with_variables(v);
716
717 let script_res = p
718 .parse_script(r#" $script:var_int = 5;$var_string = "assdfa" "#)
719 .unwrap();
720 let script_variables = script_res.script_variables();
721 assert_eq!(script_variables.get("var_int"), Some(&PsValue::Int(5)));
722 assert_eq!(
723 script_variables.get("var_string"),
724 Some(&PsValue::String("assdfa".into()))
725 );
726 }
727
728 #[test]
729 fn test_env_special_cases() {
730 let v = Variables::env();
731 let mut p = PowerShellSession::new().with_variables(v);
732 p.safe_eval(r#" $global:program = $env:programfiles + "\program" "#)
733 .unwrap();
734 assert_eq!(
735 p.safe_eval(r#" $global:program "#).unwrap().as_str(),
736 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
737 );
738 assert_eq!(
739 p.safe_eval(r#" $program "#).unwrap().as_str(),
740 format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
741 );
742
743 assert_eq!(
744 p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} = 5;${Env:ProgramFiles(x86):adsf} "#)
745 .unwrap()
746 .as_str(),
747 5.to_string()
748 );
749 assert_eq!(
750 p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
751 .unwrap()
752 .as_str(),
753 std::env::var("ProgramFiles(x86)").unwrap()
754 );
755 }
756
757 #[test]
758 fn special_last_error() {
759 let input = r#"3+"01234 ?";$a=5;$a;$?"#;
760
761 let mut p = PowerShellSession::new();
762 assert_eq!(p.safe_eval(input).unwrap().as_str(), "True");
763
764 let input = r#"3+"01234 ?";$?"#;
765 assert_eq!(p.safe_eval(input).unwrap().as_str(), "False");
766 }
767
768 #[test]
769 fn test_from_ini() {
770 let input = r#"[global]
771name = radek
772age = 30
773is_admin = true
774height = 5.9
775empty_value =
776
777[script]
778local_var = "local_value"
779 "#;
780 let mut variables = Variables::new().values_persist();
781 variables.load_from_string(input).unwrap();
782 let mut p = PowerShellSession::new().with_variables(variables);
783
784 assert_eq!(
785 p.parse_script(r#" $global:name "#).unwrap().result(),
786 PsValue::String("radek".into())
787 );
788 assert_eq!(
789 p.parse_script(r#" $global:age "#).unwrap().result(),
790 PsValue::Int(30)
791 );
792 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
793 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
794 assert_eq!(
795 p.safe_eval(r#" $script:local_var "#).unwrap().as_str(),
796 "\"local_value\""
797 );
798 assert_eq!(
799 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
800 "\"local_value\""
801 );
802 }
803
804 #[test]
805 fn test_from_ini_string() {
806 let input = r#"[global]
807name = radek
808age = 30
809is_admin = true
810height = 5.9
811empty_value =
812
813[script]
814local_var = "local_value"
815 "#;
816
817 let variables = Variables::from_ini_string(input).unwrap().values_persist();
818 let mut p = PowerShellSession::new().with_variables(variables);
819 assert_eq!(
820 p.parse_script(r#" $global:name "#).unwrap().result(),
821 PsValue::String("radek".into())
822 );
823 assert_eq!(
824 p.parse_script(r#" $global:age "#).unwrap().result(),
825 PsValue::Int(30)
826 );
827 assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
828 assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
829 assert_eq!(
830 p.safe_eval(r#" $script:local_var "#).unwrap().as_str(),
831 "\"local_value\""
832 );
833 assert_eq!(
834 p.safe_eval(r#" $local_var "#).unwrap().as_str(),
835 "\"local_value\""
836 );
837
838 assert_eq!(
839 p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
840 "\"local_value\""
841 );
842 }
843}