ps_parser/parser/
variables.rs

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    //special variables
38    // status: bool, // $?
39    // first_token: Option<String>,
40    // last_token: Option<String>,
41    // current_pipeline: Option<String>,
42}
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                // Try to parse the value as different types
161                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                // Insert the variable (overwrite if it exists and is not read-only)
174                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    /// Creates a new empty Variables container.
207    ///
208    /// # Arguments
209    ///
210    /// * initializes the container with PowerShell built-in variables like
211    ///   `$true`, `$false`, `$null`, and `$?`. If `false`,
212    ///
213    /// # Returns
214    ///
215    /// A new `Variables` instance.
216    ///
217    /// # Examples
218    ///
219    /// ```rust
220    /// use ps_parser::Variables;
221    ///
222    /// // Create with built-in variables
223    /// let vars_with_builtins = Variables::new();
224    ///
225    /// // Create empty
226    /// let empty_vars = Variables::new();
227    /// ```
228    pub fn new() -> Variables {
229        Default::default()
230    }
231
232    /// Creates a new Variables container with forced evaluation enabled.
233    ///
234    /// This constructor creates a Variables instance that will return
235    /// `Val::Null` for undefined variables instead of returning `None`.
236    /// This is useful for PowerShell script evaluation where undefined
237    /// variables should be treated as `$null` rather than causing errors.
238    ///
239    /// # Returns
240    ///
241    /// A new `Variables` instance with forced evaluation enabled and built-in
242    /// variables initialized.
243    ///
244    /// # Examples
245    ///
246    /// ```rust
247    /// use ps_parser::{Variables, PowerShellSession};
248    ///
249    /// // Create with forced evaluation
250    /// let vars = Variables::force_eval();
251    /// let mut session = PowerShellSession::new().with_variables(vars);
252    ///
253    /// // Undefined variables will evaluate to $null instead of causing errors
254    /// let result = session.safe_eval("$undefined_variable").unwrap();
255    /// assert_eq!(result, "");  // $null displays as empty string
256    /// ```
257    ///
258    /// # Behavior Difference
259    ///
260    /// - `Variables::new()`: Returns `None` for undefined variables
261    /// - `Variables::force_eval()`: Returns `Val::Null` for undefined variables
262    ///
263    /// This is particularly useful when parsing PowerShell scripts that may
264    /// reference variables that haven't been explicitly defined, allowing
265    /// the script to continue execution rather than failing.
266    pub fn force_eval() -> Self {
267        Self {
268            force_var_eval: true,
269            ..Default::default()
270        }
271    }
272
273    // not exported in this version
274    #[allow(dead_code)]
275    pub(crate) fn values_persist(mut self) -> Self {
276        self.values_persist = true;
277        self
278    }
279
280    /// Loads all environment variables into a Variables container.
281    ///
282    /// This method reads all environment variables from the system and stores
283    /// them in the `env` scope, making them accessible as
284    /// `$env:VARIABLE_NAME` in PowerShell scripts.
285    ///
286    /// # Returns
287    ///
288    /// A new `Variables` instance containing all environment variables.
289    ///
290    /// # Examples
291    ///
292    /// ```rust
293    /// use ps_parser::{Variables, PowerShellSession};
294    ///
295    /// let env_vars = Variables::env();
296    /// let mut session = PowerShellSession::new().with_variables(env_vars);
297    ///
298    /// // Access environment variables
299    /// let path = session.safe_eval("$env:PATH").unwrap();
300    /// let username = session.safe_eval("$env:USERNAME").unwrap();
301    /// ```
302    pub fn env() -> Variables {
303        let mut vars = Variables::new();
304
305        // Load all environment variables
306        for (key, value) in std::env::vars() {
307            // Store environment variables with Env scope so they can be accessed via
308            // $env:variable_name
309            vars.env
310                .insert(key.to_lowercase(), Val::String(value.into()));
311        }
312        vars
313    }
314
315    /// Loads variables from an INI configuration file.
316    ///
317    /// This method parses an INI file and loads its key-value pairs as
318    /// PowerShell variables. Variables are organized by INI sections, with
319    /// the `[global]` section creating global variables and other sections
320    /// creating scoped variables.
321    ///
322    /// # Arguments
323    ///
324    /// * `path` - A reference to the path of the INI file to load.
325    ///
326    /// # Returns
327    ///
328    /// * `Result<Variables, VariableError>` - A Variables instance with the
329    ///   loaded data, or an error if the file cannot be read or parsed.
330    ///
331    /// # Examples
332    ///
333    /// ```rust
334    /// use ps_parser::{Variables, PowerShellSession};
335    /// use std::path::Path;
336    ///
337    /// // Load from INI file
338    /// let variables = Variables::from_ini_string("[global]\nname = John Doe\n[local]\nlocal_var = \"local_value\"").unwrap();
339    /// let mut session = PowerShellSession::new().with_variables(variables);
340    ///
341    /// // Access loaded variables
342    /// let name = session.safe_eval("$global:name").unwrap();
343    /// let local_var = session.safe_eval("$local:local_var").unwrap();
344    /// ```
345    ///
346    /// # INI Format
347    ///
348    /// ```ini
349    /// # Global variables (accessible as $global:key)
350    /// [global]
351    /// name = John Doe
352    /// version = 1.0
353    ///
354    /// # Local scope variables (accessible as $local:key)
355    /// [local]
356    /// temp_dir = /tmp
357    /// debug = true
358    /// ```
359    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    /// Create a new Variables instance with variables loaded from an INI file
366    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 //todo!(),
413            }
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 //todo!(),
440            }
441            None => self.mut_top_scope(None),
442        }
443    }
444
445    /// Sets the value of a variable in the specified scope.
446    ///
447    /// # Arguments
448    ///
449    /// * `var_name` - The variable name and scope information.
450    /// * `val` - The value to assign to the variable.
451    ///
452    /// # Returns
453    ///
454    /// * `Result<(), VariableError>` - Success or an error if the variable is
455    ///   read-only.
456    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            // No scope specified, check local scopes first, then globals
491            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    /// Retrieves the value of a variable from the appropriate scope.
510    ///
511    /// # Arguments
512    ///
513    /// * `var_name` - The variable name and scope information.
514    ///
515    /// # Returns
516    ///
517    /// * `VariableResult<Val>` - The variable's value, or an error if not
518    ///   found.
519    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        // No scope specified, check local scopes first, then globals
563        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 => {} /* unreachable */
591            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}